Lagomでサービス呼び出しのIdを通じてパスから複数のパラメータを受け取る

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で指定されたパラメータの型としてStringRequestの部分にはこのサービスではリクエストは使用しないのでNotUsedResponseの部分にはこのサービスの戻り値の型として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の型パラメータを変更するだけではなく、restCallwithHelloIdのデシリアライズシリアライズを行う関数を渡しています。Scalaをやったことのある方であればケースクラスのapplyunapplyを渡していると言ったほうがわかりやすいでしょうか。こういったリフレクションに頼らない方式は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 + "!");
  }

}