コンティニュアム

長年、アプリケーションを構築する際には、クライアント・サーバ・アーキテクチャがデファクト・スタンダードとなっていました。しかし、大きな変化が起こりました。1つのモデルがすべてを支配する時代は終わりました。新しい種類のアプリケーションやアーキテクチャスタイルが登場し、コードの書き方やアプリケーションのデプロイ方法、実行方法を一変させました。HTTPマイクロサービス、リアクティブアプリケーション、イベント駆動型アーキテクチャ、サーバーレスなどが、現代のシステムの中心的な担い手となっています。

Quarkusは、この新しい世界を念頭に置いて設計されており、これらの異なるパラダイムに対して一級のサポートを提供しています。それは、Quarkusでモノリスを構築できないということではなく、スムーズに構築することができます。それどころか、Quarkusの開発モデルは、モノリス、マイクロサービス、リアクティブ、イベントドリブン、ファンクションなど、開発しているアプリケーションの種類に合わせて変化します。

HTTP マイクロサービス

まず、基本的なことから始めましょう。HTTPマイクロサービスです。ここでは、RESTやCRUDと呼ばれるHTTPエンドポイントを開発する必要があります。受信したHTTPリクエストを処理しますが、そのためにはデータベースなどの他のサービスや別のHTTPサービスに依存する必要があります。

この種のアプリケーションでは、QuarkusはJAX-RS、JPA、MicroProfile Rest Clientなどのよく知られた標準に加えて、データベースとのやり取りを簡素化するためにHibernate with Panacheにも依存しています。

周期表の元素を扱う非常にシンプルなアプリケーションを考えてみましょう。コードは次のようなものになります。

@Path("/elements")
        public class ElementResource {

    @GET
    public List<Element> getAll() {
        return Element.listAll();
    }

    @GET
    @Path("/{position}")
    public Element getOne(@PathParam("position") int position) {
        Element element = Element.find("position", position).firstResult();
        if (element == null) {
            throw new WebApplicationException("Element with position " + position + " does not exist.", 404);
        }
        return element;
    }

    @POST
    @Transactional
    public Response create(Element element) {
        element.persist();
        return Response.ok(element).status(201).build();
    }

    //...
}

Java EEやSpringのユーザーであれば、この開発モデルは見覚えがあるはずです。異なるリクエストを処理するために、@GET@POST...でアノテーションされたメソッドを含むリソースを公開します。パスは、@Pathアノテーションを使用して指定します。Quarkusは、@GetMapping@RestControllerのようなSpring controllerアノテーションもサポートしています。

JPAのエンティティマネージャーを直接使用することができます。Panacheは、ボイラープレートを取り除き、アクティブなレコードとリポジトリモデルを公開するという代替案を提示します。

Panacheでは、Elementクラスは、次のように簡単です。

@Entity
public class Element extends PanacheEntity {

    public String name;
    public String symbol;
    @Column(unique = true)
    public int position;
}

マイクロサービスはシステムとして提供されることが多いです。ここで、別のHTTPエンドポイントにアクセスする必要があるとしましょう。HTTPクライアントを直接使用することもできますが、これはお決まりのコードを繰り返すだけです。Quarkusでは、MicroProfile Rest Client APIを使用して、HTTPエンドポイントを簡単に呼び出す方法を提供しています。

まず、次のように自分のサービスを宣言します。

@Path("/elements")
@RegisterRestClient(configKey="element-service")
public interface ElementService {

    @GET
    @Path("/{position}")
    Element getElement(@PathParam("position") int position);
}

意図している各呼び出しに対して、メソッドを追加し、アノテーションを使用して動作を記述します。RESTクライアントとfault tolerance エクステンションを組み合わせることで、失敗をグレースフルに処理することができます。そして、リソースでは ElementService インターフェースを使用するだけです。

@Path("/elem")
public class ElementResource {

    @RestClient
    ElementService elements;

    @GET
    @Path("/{position}")
    public Element name(@PathParam("position") int position) {
        return elements.getElement(position);
    }
}

しかし、URLがコードに含まれていないため、どこで設定されているのか疑問に思うかもしれません。URLは環境に依存する可能性が高いので、ハードコーディングしてはいけないことを覚えておいてください。URLの設定は、アプリケーションの設定で行います。

element-service/mp-rest/url=http://localhost:9001

システムプロパティや環境変数を使って、デプロイ時や起動時にURLを更新できるようになりました。

Quarkusは、HTTPに限定されません。マイクロサービス分野の2つの有力な選択肢であるgRPCGraphQLを使用することができます。

リアクティブであること

アプリケーションの要件は、ここ数年で大きく変化しています。クラウドコンピューティング、ビッグデータ、IoTの時代にアプリケーションを成功させるためには、リアクティブにすることが、従うべきアーキテクチャのスタイルになりつつあります。

Today’s users embrace applications with milliseconds of response time, 100% uptime, lower latency, push data instead of pull, higher throughput, and elasticity. However, these features are nearly impossible to achieve using yesterday’s software architecture without a considerable investment in resources, infrastructure, and tooling. The world changed, and having dozens of servers, long response times (> 500 ms), downtime due to maintenance or waterfalls of failures does not meet the expected user experience.

Quarkusは、リアクティブへの道のりをサポートします。Quarkusは、リアクティブコアをベースにしており、アプリケーションにリアクティブ型コンポーネントと命令型コンポーネントを混在させることができます。例として、RESTEasy Reactive エクステンションを使用して、以下のようにリアクティブなHTTPエンドポイントを実装できます。

@GET
@Path("/elements/{position}")
public Uni<Element> getElement(@PathParam("position") int position) {
    return elements.getElement(position)
        .onFailure().recoverWithItem(FALLBACK);
}

Mutiny Reactive APIのおかげで、非同期操作を構成し、I/Oスレッドをブロックすることなく、すべてが完了したときに結果を完了することができます。これにより、リソース消費量と弾力性が大幅に向上します。ほとんどのQuarkus APIは、命令型とリアクティブ型の両方で利用できます。例えば、REST Clientのリアクティブバージョンを使用することができます。

@Path("/elements")
@RegisterRestClient(configKey="element-service")
public interface ElementService {

    @GET
    @Path("/{position}")
    Uni<Element> getElement(@PathParam("position") int position);
}

しかし、ストリームについてはどうでしょうか?Quarkusでserver-sent eventレスポンスを生成するのは、次のようにとても簡単です。

@Produces(MediaType.SERVER_SENT_EVENTS)
@GET
@Path("/events")
public Multi<String> stream() {
    return kafka.toMulti();
}

イベントドリブンなアーキテクチャー

しかし、HTTPの特性上、すべてのコンポーネントが非同期のメッセージパッシングを使って相互に作用するようなリアクティブシステムを実装することはできません。

まず、AMQPやApache Kafkaなどの各種ブローカーからのメッセージを消費し、そのメッセージをスムーズに処理することができます。

@ApplicationScoped
public class MyEventProcessor {

  @Incoming("health")
  @Outgoing("heartbeat")
  public double extractHeartbeat(Health health) {
    return health.getHeartbeat();
  }
}

@Incoming@Outgoingのアノテーションは、Reactive Messagingの一部です。これらは、どのチャネルから消費しているか、どのチャネルに送信しているかを表現するために使用されます。Reactive Messaging のおかげで、HTTP、Kafka、Apache Camelなど、さまざまなブローカーやトランスポートからメッセージを受信したり、それらに送信したりすることができます。

メッセージを1つ1つ処理するだけでは不十分な場合があります。次の例のように、メッセージ処理ロジックをリアクティブ・プログラミングで表現することもできます。

@Incoming("health")
@Outgoing("output")
public Multi<Record<String, Measure> filterState(Multi<Capture> input) {
    return input
        .drop().repetitions()
        .select().where(capture -> capture.value > 0)
        .onItem().transform(capture -> new Measure(capture.sensor, capture.value, capture.unit))
        .onItem().transform(measure -> Record.of(measure.sensor, measure));
}

Quarkusが公開しているリアクティブAPIについては、ストリーム操作にMutiny APIを使用しています。

Functions as a Service とサーバーレス

驚異的な起動時間と低いメモリ使用量のおかげで、Quarkusを使って関数を実装し、サーバーレス環境で使用することができます。Quarkusは、関数を書くためのアプローチであるFunqyを提供しており、AWS Lambda、Azure Functions、Knative、Knative Events(Cloud Events)など、さまざまなFaaS環境にデプロイ可能です。また、スタンドアロンのサービスとしても使用可能です。

Funqyでは、関数は単に次のようになります。

import io.quarkus.funqy.Funq;

public class GreetingFunction {
    @Funq
    public String greet(String name) {
       return "Hello " + name;
    }
}

関数内でQuarkusのあらゆる機能を使用することができ、高速起動と低メモリ使用の恩恵を受けることができます。Quarkusでは、プログラミング言語を変更することなく、この新しい世界を受け入れることができます。