Akka HTTPの概要

1. 概要

このチュートリアルでは、Akkaのlink:/akka-actors-java[Actor]&link:/akka-streams[Stream]モデルの助けを借りて学習します基本的なCRUD操作を提供するHTTP APIを作成するためにAkkaをセットアップする方法。

2. Mavenの依存関係

まず、Akka HTTPの使用を開始するために必要な依存関係を見てみましょう。
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-http_2.12</artifactId>
    <version>10.0.11</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-stream_2.12</artifactId>
    <version>2.5.11</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-http-jackson_2.12</artifactId>
    <version>10.0.11</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-http-testkit_2.12</artifactId>
    <version>10.0.11</version>
    <scope>test</scope>
</dependency>
もちろん、これらのAkkaライブラリの最新バージョンはhttps://search.maven.org/search?q=com.typesafe.akka[Maven Central]で見つけることができます。

3. アクターを作成する

例として、ユーザーリソースを管理できるHTTP APIを作成します。 APIは2つの操作をサポートします。
  • 新しいユーザーを作成する

  • 既存のユーザーの読み込み

    HTTP APIを提供する前に、*必要な操作を提供するアクターを実装する必要があります:*
class UserActor extends AbstractActor {

  private UserService userService = new UserService();

  static Props props() {
    return Props.create(UserActor.class);
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
      .match(CreateUserMessage.class, handleCreateUser())
      .match(GetUserMessage.class, handleGetUser())
      .build();
  }

  private FI.UnitApply<CreateUserMessage> handleCreateUser() {
    return createUserMessage -> {
      userService.createUser(createUserMessage.getUser());
      sender()
        .tell(new ActionPerformed(
           String.format("User %s created.", createUserMessage.getUser().getName())), getSelf());
    };
  }

  private FI.UnitApply<GetUserMessage> handleGetUser() {
    return getUserMessage -> {
      sender().tell(userService.getUser(getUserMessage.getUserId()), getSelf());
    };
  }
}
基本的に、_AbstractActor_クラスを拡張し、その_createReceive()_メソッドを実装しています。
_createReceive()_内では、それぞれのタイプのメッセージを処理するメソッドに*着信メッセージタイプ*をマッピングしています。
*メッセージタイプは、特定の操作を説明するいくつかのフィールドを持つ単純なシリアライズ可能なコンテナクラスです*。 _GetUserMessage_には、ロードするユーザーを識別する単一のフィールド_userId_があります。 _CreateUserMessage_には、新しいユーザーを作成するために必要なユーザーデータを含む_User_オブジェクトが含まれます。
後で、着信HTTP要求をこれらのメッセージに変換する方法について説明します。
最終的に、すべてのメッセージを_UserService_インスタンスに委任します。これにより、永続的なユーザーオブジェクトの管理に必要なビジネスロジックが提供されます。
また、_props()_メソッドにも注意してください。 * __props()__methodは拡張には必要ありませんが、_ * AbstractActor *、_は後で_ActorSystem_を作成するときに役立ちます。
俳優についてのより詳細な議論については、https://www.baeldung.com/akka-actors-java [Akka Actorsの紹介]をご覧ください。

4.  HTTPルートの定義

実際の作業を行うアクターがいるので、あとは、着信HTTP要求をアクターに委任するHTTP APIを提供するだけです。
Akkaは、ルートの概念を使用してHTTP APIを記述します。 *各操作には、ルートが必要です。*
HTTPサーバーを作成するには、フレームワーククラス_HttpApp_を拡張し、_routes_メソッドを実装します。
class UserServer extends HttpApp {

  private final ActorRef userActor;

  Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));

  UserServer(ActorRef userActor) {
    this.userActor = userActor;
  }

  @Override
  public Route routes() {
    return path("users", this::postUser)
      .orElse(path(segment("users").slash(longSegment()), id -> route(getUser(id))));
  }

  private Route getUser(Long id) {
    return get(() -> {
      CompletionStage<Optional<User>> user =
        PatternsCS.ask(userActor, new GetUserMessage(id), timeout)
          .thenApply(obj -> (Optional<User>) obj);

      return onSuccess(() -> user, performed -> {
        if (performed.isPresent())
          return complete(StatusCodes.OK, performed.get(), Jackson.marshaller());
        else
          return complete(StatusCodes.NOT_FOUND);
      });
    });
  }

  private Route postUser() {
    return route(post(() -> entity(Jackson.unmarshaller(User.class), user -> {
      CompletionStage<ActionPerformed> userCreated =
        PatternsCS.ask(userActor, new CreateUserMessage(user), timeout)
          .thenApply(obj -> (ActionPerformed) obj);

      return onSuccess(() -> userCreated, performed -> {
        return complete(StatusCodes.CREATED, performed, Jackson.marshaller());
      });
    })));
  }
}
さて、ここにはかなりの定型文がありますが、**マッピング操作の前と同じパターン、今度はルートとして従うことに注意してください。 **少し分解しましょう。
_getUser()_内で、_GetUserMessage_型のメッセージで着信ユーザーIDをラップし、そのメッセージを_userActor_に転送します。
アクターがメッセージを処理すると、_onSuccess_ハンドラーが呼び出されます。このハンドラーでは、特定のHTTPステータスと特定のJSONボディを持つ応答を送信することで、HTTP要求を_complete_します。 link:/jackson-object-mapper-tutorial[Jackson] marshallerを使用して、アクターからの回答をJSON文字列にシリアル化します。
_postUser()_内では、HTTPリクエストにJSONボディが必要であるため、少し異なる方法で処理します。 _entity()_メソッドを使用して、着信JSONボディを_User_オブジェクトにマッピングしてから、_CreateUserMessage_にラップしてアクターに渡します。 ここでも、Jacksonを使用してJavaとJSONの間のマッピングを行います。
  • _HttpApp_は単一の_Route_オブジェクトを提供することを期待しているため、_routes_メソッド内で両方のルートを単一の_Route_に結合します。

    _postUser()_によって提供されるルートをパス_ / users_にバインドします。 着信リクエストがPOSTリクエストではない場合、Akkaは自動的に_orElse_ブランチに入り、パスが_ / users / <id> _になることを期待します。 着信HTTPメソッドに応じて、要求は_getUser()_ routeに転送されるか、まだ一致しない場合はHTTPステータス404を返します。
    AkkaでHTTPルートを定義する方法の詳細については、https://doc.akka.io/docs/akka-http/current/routing-dsl/routes.html [Akka docs]をご覧ください。

5. サーバーの起動

上記のような_HttpApp_実装を作成したら、数行のコードでHTTPサーバーを起動できます。
public static void main(String[] args) throws Exception {
  ActorSystem system = ActorSystem.create("userServer");
  ActorRef userActor = system.actorOf(UserActor.props(), "userActor");
  UserServer server = new UserServer(userActor);
  server.startServer("localhost", 8080, system);
}
*タイプ_UserActor_の単一のアクターで_ActorSystem_を作成し、_localhost_でサーバーを起動します。*

6. 結論

この記事では、Akka HTTPの基本について学び、HTTPサーバーをセットアップし、エンドポイントを公開して、REST APIと同様にリソースを作成およびロードする方法を示します。
いつものように、ここで紹介するソースコードはhttps://github.com/eugenp/tutorials/tree/master/akka-http[GitHub]で見つけることができます。