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
への適合をサポートします。
- 単純なスカラー(
Integer
、String
、Long
など) - Retrofit
Response
- Ratpack
ReceivedResponse
次の例は、同じエンドポイントに対して構成された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-retrofit2
はScalarsConverterFactory
を登録します。これにより、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();
});
}
}