yu nkt’s blog

nkty blog

I'm an enterprise software and system architecture. This site dedicates sharing knowledge and know-how about system architecture with me and readers.

アクターモデルとアプリケーションアーキテクチャの関係

背景

マイクロサービスアーキテクチャが浸透し、それに伴いDDDを導入する企業も増えている気がします。 それと同時に、アクターモデルの話題も最近以前より聞くようになった気がします。 ただ、以下のような疑問を持つ人は多くいるのではないでしょうか?

  • アクターモデルは聞いたことがあるけど、重要性が分からない
  • 使い所が分からない
  • サーバーレスコンピューティングなの?でもAkkaの説明ばかり出てくるけど?

こういう状況になっている要因の一つは、おそらく、アクターモデルの説明の多くが分散システムにフォーカスしており(当たり前なんですが)、アプリケーションアーキテクチャとの関係性については、使う人まかせになっているためではないでしょうか。

ここでは、アプリケーションアーキテクチャと合わせて、アクターモデルの使い所を考えてみます。

先に結論

アクターモデルは、分散環境で実行するアプリケーションを開発するためのアプリケーションモデル(考え方)なので、スケールアウトに伴う、スループットや耐障害性の向上に寄与するものです。

その上で、アプリケーションアーキテクチャの観点で考えた時、アクターモデルは、以下のシチュエーションで効果的に使える気がします。

  • DDDを適用して開発されているマイクロサービスが、分散環境で実行されるとした上で、
    • データの整合性を担保しやすくするために、ドメイン集約は相関IDごとに一箇所(アクター)で実行
    • CQRSにおいて、コマンドからのイベントの発行と、イベント列(ジャーナル)からクエリ用データ(プロジェクション)の作成は、整合性のためシーケンシャルに実行

総じて言うと、

  • アプリケーションは基本的に、複数のCPU/マシンで分散したい
  • ただ、データの一貫性を保証しやすくするためなど、一箇所でのみ行いたい部分もある

という場合において、アクターモデルが効果的だと思われます。 その特徴を、DDDやマイクロサービスアーキテクチャに応用すれば良いと思います。

アクターモデルとは

概要

SlideshareやSpeakerDeckなどで、多くの解説スライドがあります。 アクターモデルは、Akkaという代表的なフレームワークがあるので、往々にしてそれとセットで紹介されています。 例えば、こちらを御覧ください。

www.slideshare.net

要点は以下の通りだと思います。

  • 1つのアプリケーションは、複数の細切れの処理の集合とみなす (クラス、関数)
  • 細切れの処理は、アクターという処理を行う実体で実行される
  • アプリケーションの処理は、アクター間の連携で実現される
  • アクター間の連携は、あるアクターが別のアクターに"メッセージ"を送り、受け取ったアクターはメッセージに応じた処理を非同期的に行うことで実現される
  • メッセージは、ネットワークで転送されうるデータであり、語弊を恐れず言うと、いわば処理のコールをパケット化したものである
  • アクターは、別のアクターを生成できる
  • 各アクターは(メモリ上に)状態を持ちうる

つまり、アクターがそれぞれアプリ内の一部のロジックを持ち、受け取ったメッセージに応じて処理を実行し、必要があれば別のアクターにメッセージパッシングする、ということです。

アクターモデルと分散処理

アクターモデルを調べると、必ず分散処理、というキーワードがヒットします。 それは当然で、アクターモデルは複数のCPUのある環境でスケーラブルに動くアプリケーションを作るために考えられた、アプリケーション設計モデルだからです。

ポイントは、アプリケーションの処理を、細切れにして、その細切れをアクターという実体で、非同期的に処理するところがポイントです。 アクターは、それぞれ非同期的に処理を実行し、送り合うメッセージは、ネットワークを介して交換されるので、複数のアクターは同じCPUにある必要がありません。 リソースの空いているCPUにアクターを用意し、そこにメッセージを送って処理し終わったらアクターを消せばよいのです。 そのため、分散環境を効率的に活用できます。

サーバーレスコンピューティングと同じか?

結局それって、AWS Lambdaのようなもの?そう思う方は多いと思います。 アクターモデルは、サーバーレスコンピューティングなのでしょうか?

アプリケーションアーキテクチャの部分を想像して近いと言っているなら、大きく間違ってはいない気もします。 コンテナなどのインフラのことは置いといて、AWS Lambda上で書く、Pythonなどのコード片(関数)の方を想像してください。 このコード片の集合でアプリケーションが実現されます。 サーバーレスコンピューティングも、そういったコード片の連携によって実行されます。

ただし、AWS Lambdaのような関数は、開発の単位は関数ごとになるかもしれません。 そのため、アプリケーション全体の見通しは良くないでしょう。 Serverless frameworkなどを使うことで、アプリケーション単位でコードベースをまとめることはできるかもしれませんが、関数間の関係性をしっかり管理しておく必要があります。

アクターモデルは、アプリケーションを一つのコードベースで管理し、見通しが良いまま、処理の一部を複数のサーバーで実行できます。

DockerやKubernetesと同じか?

違います。アクターモデルは、アプリケーションモデル(アプリケーションの構造の有り様)です。

これらの違いをしっかり説明するなら、Docker/KubernetesとAkkaのアクターが、「どういった単位で分散されるのか」を考えるのが良いです。 DockerやKubernetesでデプロイされるのは、アプリケーションの単位です。 ここでいう、アプリケーションは、往々にして、ビジネス上の関心としてのひとまとまりです。 DDDで言うと、一つのドメイン境界に相当するものです。 Gitでは一つのプロジェクトとなります。 要するに一つのマイクロサービスです。 旅行予約サイトというシステムや、オークションサイトというシステムは、顧客サービス、会計サービス、ホテルサービス、などの複数のアプリケーション(マイクロサービス)で構成されているイメージです。

これに対して、アクターは、アプリケーション内に含まれる処理です。 そのため、これらの関係性は以下のようになります。

マルチコアプログラミングってこと?

では、要するにマルチコアプログラミングに近い、ということでしょうか? JavaならExecutor Frameworkでマルチコアで実行できる処理を実装できますが、それを使えばアクターモデルは不要でしょうか?

やや捉え方が違います。Executor Frameworkは、複数のCPUにスレッドを立てて処理を並列実行するための、実装の話です。 アクターモデルは、アプリケーションの構造の有り様の一つです。 どの処理を複数プロセスで並列化するか、どの処理をシーケンシャルに処理するか、が表現できる分散プログラムの表現形式、といったところでしょうか…。 マルチコアプログラミングより抽象レベルの高い話です。

DDDとアクターモデルの関係

直接、DDDとアクターモデルが関係を持っているわけではありません。 ただ、DDDによる設計を実装するにあたって、主にDDDの以下の概念を実現する手段としてアクターモデルを利用すると、効率的に実装ができると思います。

トランザクション境界

DDDには集約という概念があります。 ドメインオブジェクトの集合なのですが、含まれるドメインオブジェクト間の関係性などに一定のルールがあり(不変条件)、その整合性が保たれていることが期待されます。 そのため、集約に対する状態の更新は、その集約に含まれるドメインオブジェクト全体で、単一のトランザクションになるように行わなければなりません。(下記ページ参照)

実践DDD本 第10章「集約」~トランザクション整合性を保つ境界~ (1/3):CodeZine(コードジン)

ただ、もし、アプリケーションが一般的な単一のプロセスで実行されるものだとして、それがKubernetesなどで複数のPodで動いているとしたら、そのトランザクションは、ロックをどう取りましょうか? 面倒だと思います。できないことはありません。ただ、アプリ開発者が書きたいコードですか?という話です。

アクターモデルを用いる場合、集約のIDごとにアクターを一つだけ用意します(集約ルートアクターと呼んだりします)。 アクターが一つ、ということは、このトランザクションの処理は、一つのプロセスだけで実行されるということです。 そのため、ほぼ同時にトランザクションのリクエストが来ても、一瞬後のリクエストは前の処理を待つことになり、シーケンシャルに処理されます。

かとじゅんさんのブログの補足と書いている部分がとても参考になります。

blog.j5ik2o.me

IDの違う集約は別のアクターで処理されるため、分散環境なのにある一箇所だけで処理される、とは言えどもそれなりにはスケールすると思います。 もし特定のIDの集約だけ異常に処理される場合は、集約の設計自体を見直すのも手かもしれません。

コマンドとクエリ

CQRS/Event Sourcingによって、ドメインによりフォーカスした設計が出来ます。上のかとじゅんさんのブログがとても参考になります。

CQRSとは、WriteモデルとReadモデルを分けて、それらをイベント駆動で連携させることにより、Write側とRead側で有用なモデルを利用する、というアーキテクチャパターンです。 Write側では、ドメインオブジェクトに、データ変更を含む何かの操作(コマンド)を要求し、それが完了したら、何かが起こったことを示すドメインイベントを発行します。 Write側もRead側もこのドメインイベントの羅列から、ドメインオブジェクトの状態を復元します。これがイベントソーシングです。 Read側は、発行されたイベントを検知し、ドメインイベントの羅列とともに、Read側に必要な情報を付加したり、データの構造を変えて、Readモデルを生成します。

CQRS/Event Sourcingに関しては、こちらの記事も参考になるかと思います。

rheb.hatenablog.com

Writeの方は、集約・トランザクション境界のところで説明したとおり、IDごとに単一のアクターを用意します。

またWrite側で、もう一つ、アクターが活きる場面があります。 DDDで多く行われる処理の一つは、①サービス層にて、ファクトリからドメインオブジェクトを生成し、②そのドメインオブジェクトに何らかの処理を依頼し、③得られたオブジェクトをリポジトリを介して永続化する、という流れだと思います。 このDBから取り出す操作を、この処理のたびに行う必要がないように、状態を持つアクターを用意して、インメモリで実現することが出来ます。 ドメインオブジェクトの、データベースで言うマテリアライズドビューを、アクターで実現できます。

Read側も、イベントストアに保存されたドメインイベントの羅列(ジャーナルと呼ばれる)から、Readモデルで必要なデータ(プロジェクションと呼ばれる)を作る必要があります。 これはシーケンシャルに実行するべきなので、ここでもアクターを使うのが有効です。

これらは、こちらのスライドで、アーキ図を含めて解説されています。

www.slideshare.net

重要なのは、こういったWriteとReadは、ビジネス上の観点で分かれるものではなく、同じドメインに含まれるため、一つのコードベース(一つのプロジェクト)に入れておきたいはずです。 シーケンシャルに行うべき部分と、並列化して実行したい部分を、一つのアプリケーションのプロジェクトに入れ込めるのは、インフラの観点からコードベースを分けるのではなく、ドメインの観点でコードベースを分けられる、という意味で、とても意義深いものだと思います。

結び

エンタープライズシステムでは、設計指針を持った開発が必要です。 一つの関心は一つの場所にまとめ、不用意な依存性を作らず、アプリケーションが進化していけるように、適切なアプリケーションアーキテクチャが必要です。

そういった指針として、現在はDDDが主流だと思うので、その観点で、アクターモデルがどう利用できるのか、を考えてみました。 アクターモデルを抽象レベルの高いアプリケーションアーキテクチャから考えてみた結果、上記の2つが主な使い所なのかなと考えています。 まだまだ、きっと多くの使い所があると思いので、またはっきりしたら、追記するかもしれません。