このマニュアルは進行中の作業であり、現在は不完全な状態です。
改善のお手伝いをしていただけると幸いです。ご協力をお待ちしております。詳細についてはREADMEを参照してください。

21 Retrofit型安全クライアント

ratpack-retrofit2拡張機能は、retrofit2ライブラリ(v2.9.0)を使用して宣言型の型安全HTTPクライアントを統合します。

retrofitライブラリによって、型安全インターフェイスを介してHTTP APIを表すことができます。これにより、アプリケーションコードは基盤のAPI設計や実装とは無関係のまま残り、APIとインターフェイスする動作の側面のみに集中できます。

RatpackRetrofitクラスを使用して生成されたRetrofitクライアントは、RatpackのHttpClientによってサポートされており、RatpackのPromise構造を戻り値としてインターフェイスすることができます。

ratpack-retrofit2統合機能を使用することで、開発者はAPI構造をクラスやメソッドの注釈として隔離するメリットを得ることができ、同時に、Nettyによってサポートされる非同期処理の性質とRatpackの実行モデルを利用できます。

1.21 使用方法

RatpackRetrofit.client(URI endpoint)メソッドとRatpackRetrofit.client(String endpoint)メソッドは、APIクライアントを作成するためのエントリポイントを提供します。

提供されたURIまたはStringは、Builderによって生成されたすべてのクライアントのベースURLを指定します。

基盤のRetrofit.Builderを、RatpackRetrofit.Builder.configure(Action<? super Retrofit.Builder> spec)メソッドを使用して構成できます。

構成したら、クライアントはAPIインターフェイスを使用してbuild(Class<T> api)を呼び出すことによって構築されます。このメソッドは、HTTPリクエストを発行して応答を構成された戻り値の種類に合わせて調整するインターフェイスの生成されたインスタンスを返します。

import ratpack.exec.Promise;
import ratpack.retrofit.RatpackRetrofit;
import ratpack.test.embed.EmbeddedApp;
import retrofit2.http.GET;

import static org.junit.jupiter.api.Assertions.*;

public class Example {

  public static interface HelloApi {
    @GET("hi") Promise<String> hello();
  }

  public static void main(String... args) throws Exception {
    EmbeddedApp api = EmbeddedApp.of(s -> s
      .handlers(chain -> chain
        .get("hi", ctx -> ctx.render("hello"))
      )
    );
    EmbeddedApp.of(s -> s
      .registryOf(r -> 
        r.add(HelloApi.class,
          RatpackRetrofit
            .client(api.getAddress())
            .build(HelloApi.class))
      )
      .handlers(chain -> {
        chain.get(ctx -> {
          HelloApi helloApi = ctx.get(HelloApi.class);
            
          ctx.render(helloApi.hello());
        });
      })
    ).test(httpClient -> {
      assertEquals("hello", httpClient.getText());
      api.close();
    });
  }
}

2.21 Retrofit APIでRatpack Promisesを使用する

ratpack-retrofit2統合機能は、RatpackのPromiseをクライアントインターフェイスの戻り値として使用する際のサポートを提供します。約束された値が以下の型の場合にPromiseへの適合をサポートします。

次の例は、同じエンドポイントに対して構成された3つの変種を示します。

import ratpack.exec.Promise;
import ratpack.core.http.client.ReceivedResponse;
import retrofit2.Response;
import retrofit2.http.GET;

public interface Example {
  @GET("hi") Promise<String> hello();
  
  @GET("hi") Promise<Response<String>> helloResponse();
  
  @GET("hi") Promise<ReceivedResponse> helloRaw();
}

3.21 複数のAPI実装を作成する

多くのAPIは、さまざまなケイパビリティを表す明確なインターフェイスを使用して表現できます。複数のクライアントを作成するには、基盤のRetrofitクラスを取得できます。

import ratpack.exec.Promise;
import ratpack.retrofit.RatpackRetrofit;
import ratpack.test.embed.EmbeddedApp;
import retrofit2.http.GET;
import retrofit2.Retrofit;

import static org.junit.jupiter.api.Assertions.*;

  
public class Example {

  public static interface HelloApi {
    @GET("hi") Promise<String> hello();
  }
  
  public static interface GoodbyeApi {
    @GET("bye") Promise<String> bye();
  }

  public static void main(String... args) throws Exception {
    EmbeddedApp api = EmbeddedApp.of(s -> s
      .handlers(chain -> chain
        .get("hi", ctx -> ctx.render("hello"))
        .get("bye", ctx -> ctx.render("goodbye"))
      )
    );
    EmbeddedApp.of(s -> s
      .registryOf(r -> {
        Retrofit retrofit = RatpackRetrofit
          .client(api.getAddress())
          .retrofit();
        r.add(HelloApi.class, retrofit.create(HelloApi.class));
        r.add(GoodbyeApi.class, retrofit.create(GoodbyeApi.class));
      })
      .handlers(chain -> {
        chain.get(ctx -> {
            
          HelloApi hiApi = ctx.get(HelloApi.class);
          GoodbyeApi byeApi = ctx.get(GoodbyeApi.class);
            
          ctx.render(hiApi.hello().right(byeApi.bye()).map(p -> p.left() + " and " + p.right()));
        });
      })
    ).test(httpClient -> {
      assertEquals("hello and goodbye", httpClient.getText());
      api.close();
    });
  }
}

4.21 Retrofitコンバーターを使用する

既定では、ratpack-retrofit2ScalarsConverterFactoryを登録します。これにより、API応答をJava String、プリミティブ、およびボックス型に変換できます。

リモートAPIがJSONで応答している場合は、JacksonConverterFactoryを登録する必要があります。

import com.fasterxml.jackson.databind.ObjectMapper;
import ratpack.exec.Promise;
import ratpack.retrofit.RatpackRetrofit;
import ratpack.exec.registry.Registry;
import ratpack.test.embed.EmbeddedApp;
import retrofit2.converter.jackson.JacksonConverterFactory;
import retrofit2.http.GET;
import retrofit2.Retrofit;

import java.util.List;

import static ratpack.core.jackson.Jackson.json;
import static org.junit.jupiter.api.Assertions.*;

  
public class Example {

  public static interface NameApi {
    @GET("names") Promise<List<String>> names();
  }

  public static void main(String... args) throws Exception {
    EmbeddedApp api = EmbeddedApp.of(s -> s
      .handlers(chain -> chain
        .get("names", ctx -> ctx.render(json(new String[]{"John", "Jane"})))
      )
    );
    EmbeddedApp.of(s -> s
      .registry(r -> 
        Registry.single(NameApi.class, 
          RatpackRetrofit
            .client(api.getAddress())
            .configure(b -> 
              b.addConverterFactory(
                JacksonConverterFactory.create(
                  r.get(ObjectMapper.class)
                )
              )
            )
            .build(NameApi.class))
      )
      .handlers(chain -> {
        chain.get(ctx -> {
          ctx.get(NameApi.class).names().then(nameList -> ctx.render(json(nameList)));  
        });
      })
    ).test(httpClient -> {
      assertEquals("[\"John\",\"Jane\"]", httpClient.getText());
      api.close();
    });
  }
}