1. 概要

このチュートリアルでは、TwitterのRPCライブラリであるFinagleについて簡単に説明します。

これを使用して、単純なクライアントとサーバーを構築します。

2. ビルディングブロック

実装を掘り下げる前に、アプリケーションの構築に使用する基本的な概念を理解する必要があります。 それらは広く知られていますが、Finagleの世界ではわずかに異なる意味を持つ可能性があります。

2.1. サービス

サービスは、リクエストを受け取り、操作の最終結果または障害に関する情報を含むFutureを返すクラスによって表される関数です。

2.2. フィルタ

フィルタも関数です。 彼らはリクエストとサービスを受け取り、リクエストに対していくつかの操作を実行し、それをサービスに渡し、結果の Future に対していくつかの操作を実行し、最後に最終的なFutureを返します。 これらは、関数の実行の前後で発生するロジックを実装し、その入力と出力を変更できるため、アスペクトと考えることができます。

2.3. 先物

Futuresは、非同期操作の最終的な結果を表します。 それらは、保留中、成功、または失敗の3つの状態のいずれかにある可能性があります。

3. サービス

まず、単純なHTTPgreeting serviceを実装します。 リクエストからnameパラメータを取得して応答し、通常の「Hello」メッセージを追加します。

そのためには、Finagleライブラリから抽象 Service クラスを拡張するクラスを作成し、そのapplyメソッドを実装する必要があります。

私たちが行っていることは、機能インターフェイスの実装に似ています。 ただし、興味深いことに、FinagleはScalaで記述されており、Java-Scalaの相互運用性を利用しているため、この特定の機能を実際に使用することはできません。

public class GreetingService extends Service<Request, Response> {
    @Override
    public Future<Response> apply(Request request) {
        String greeting = "Hello " + request.getParam("name");
        Reader<Buf> reader = Reader.fromBuf(new Buf.ByteArray(greeting.getBytes(), 0, greeting.length()));
        return Future.value(Response.apply(request.version(), Status.Ok(), reader));
    }
}

4. フィルター

次に、リクエストに関するデータをコンソールに記録するフィルターを作成します。 Service と同様に、Filterapplyメソッドを実装する必要があります。このメソッドは、リクエストを受け取り、Futureレスポンスを返しますが今回は、サービスも2番目のパラメーターとして使用します。

基本的なFilterクラスには4つの型パラメーターがありますが、多くの場合、フィルター内の要求と応答の型を変更する必要はありません。

そのために、4つのタイプパラメータを2つにマージするSimpleFilterを使用します。 リクエストからいくつかの情報を出力してから、提供されたサービスからapplyメソッドを呼び出すだけです。

public class LogFilter extends SimpleFilter<Request, Response> {
    @Override
    public Future apply(Request request, Service<Request, Response> service) {
        logger.info("Request host:" + request.host().getOrElse(() -> ""));
        logger.info("Request params:");
        request.getParams().forEach(entry -> logger.info("\t" + entry.getKey() + " : " + entry.getValue()));
        return service.apply(request);
    }
}

5. サーバ

これで、サービスとフィルターを使用して、実際に要求をリッスンして処理するサーバーを構築できます。

このサーバーに、andThenメソッドでチェーンされたフィルターとサービスの両方を含むサービスをプロビジョニングします。

Service serverService = new LogFilter().andThen(new GreetingService()); 
Http.serve(":8080", serverService);

6. クライアント

最後に、サーバーにリクエストを送信するクライアントが必要です。

そのために、FinagleのHttpクラスの便利なnewServiceメソッドを使用してHTTPサービスを作成します。 リクエストの送信は直接の責任となります。

さらに、以前に実装したものと同じロギングフィルターを使用し、HTTPサービスとチェーンします。 次に、applyメソッドを呼び出す必要があります。

その最後の操作は非同期であり、その最終的な結果はFutureインスタンスに保存されます。この Future が成功または失敗するのを待つことができますが、それはブロッキング操作になるため、それを避けてください。 代わりに、Futureが成功したときにトリガーされるコールバックを実装できます。

Service<Request, Response> clientService = new LogFilter().andThen(Http.newService(":8080"));
Request request = Request.apply(Method.Get(), "/?name=John");
request.host("localhost");
Future<Response> response = clientService.apply(request);

Await.result(response
        .onSuccess(r -> {
            assertEquals("Hello John", r.getContentString());
            return BoxedUnit.UNIT;
        })
        .onFailure(r -> {
            throw new RuntimeException(r);
        })
);

BoxedUnit.UNITを返すことに注意してください。Unit を返すことは、 void メソッドに対処するScalaの方法であるため、相互運用性を維持するためにここで行います。

7. 概要

このチュートリアルでは、Finagleを使用して単純なHTTPサーバーとクライアントを構築する方法と、それらの間の通信を確立してメッセージを交換する方法を学びました。

いつものように、すべての例を含むソースコードは、GitHubにあります。