Lagomを試してみるシリーズの第2回です。前回はLagomを使用したサービスの最も簡単な実装例として、以下のようなサービスを定義しました。
public interface HelloService extends Service { ServiceCall<String, NotUsed, String> hello(); @Override default Descriptor descriptor() { // @formatter:off return named("helloservice").with( restCall(Method.GET, "/api/hello/:id", hello()) ).withAutoAcl(true); // @formatter:on } }
サービスのインターフェースを定義しているのは以下の部分です。
ServiceCall<String, NotUsed, String> hello();
ServiceCall
の型パラメータを3つ指定しています。ServiceCall
の定義は以下のようになっています。
@FunctionalInterface public interface ServiceCall<Id, Request, Response> { ... CompletionStage<Response> invoke(Id id, Request request) ... }
Id
の部分はパスの:id
で指定されたパラメータの型としてString
、Request
の部分にはこのサービスではリクエストは使用しないのでNotUsed
、Response
の部分にはこのサービスの戻り値の型としてString
をそれぞれ指定しています。
Id
は各サービス呼び出しの識別子という意味合いですが、LagomのサービスではId
を通じてルーティングの情報を受け取ることができます。
ではパスに以下のように複数のパラメータを指定してそれをサービスで受け取りたい場合はどうすればよいのでしょうか?
restCall(Method.GET, "/api/hello/:area/:name", hello())
この場合はId
としてパラメータに対応した複数のプロパティを持つクラスを作成し、String
の代わりにそのクラスを使用するようにします。
public class HelloId { public String area; public String name; public HelloId(String area, String name){ this.area = area; this.name = name; } }
サービスの定義はこんな感じ。
public interface HelloService extends Service { ServiceCall<HelloId, NotUsed, String> hello(); @Override default Descriptor descriptor() { // @formatter:off return named("helloservice").with( restCall(Method.GET, "/api/hello/:area/:name", hello()) .with(IdSerializers.create("HelloId", HelloId::new, id -> Arrays.asList(id.area, id.name))) ).withAutoAcl(true); // @formatter:on } }
ServiceCall
の型パラメータを変更するだけではなく、restCall
にwith
でHelloId
のデシリアライズとシリアライズを行う関数を渡しています。Scalaをやったことのある方であればケースクラスのapply
とunapply
を渡していると言ったほうがわかりやすいでしょうか。こういったリフレクションに頼らない方式はJavaプログラマの方にはやや取っつきづらさがあるかもしれません。
サービスの実装は以下のようになります。id
としてHelloId
のインスタンスが渡ってきます。
public class HelloServiceImpl implements HelloService { @Override public ServiceCall<HelloId, NotUsed, String> hello() { return (id, request) -> completedFuture("Hello, " + id.name + " in " + id.area + "!"); } }