この質問は、しばらくの間私を悩ませています (私だけではないといいのですが)。典型的な 3 層の Java EE アプリを取り上げて、それがアクターでどのように実装されるかを見てみたいと思います。そのような移行を行うことが実際に意味があるかどうか、また、それが意味をなす場合にどのように利益を得ることができるかを調べたいと思います (おそらく、パフォーマンス、より良いアーキテクチャ、拡張性、保守性など...)。
典型的なコントローラー (プレゼンテーション)、サービス (ビジネス ロジック)、DAO (データ) は次のとおりです。
trait UserDao {
def getUsers(): List[User]
def getUser(id: Int): User
def addUser(user: User)
}
trait UserService {
def getUsers(): List[User]
def getUser(id: Int): User
def addUser(user: User): Unit
@Transactional
def makeSomethingWithUsers(): Unit
}
@Controller
class UserController {
@Get
def getUsers(): NodeSeq = ...
@Get
def getUser(id: Int): NodeSeq = ...
@Post
def addUser(user: User): Unit = { ... }
}
このようなものは、多くの春のアプリケーションで見つけることができます。同期されたブロックがないため、共有状態を持たない単純な実装を使用できます...したがって、すべての状態はデータベースにあり、アプリケーションはトランザクションに依存します。サービス、コントローラー、および dao にはインスタンスが 1 つしかありません。したがって、リクエストごとにアプリケーションサーバーは個別のスレッドを使用しますが、スレッドは互いにブロックしません (ただし、DB IO によってブロックされます)。
アクターで同様の機能を実装しようとしているとします。次のようになります。
sealed trait UserActions
case class GetUsers extends UserActions
case class GetUser(id: Int) extends UserActions
case class AddUser(user: User) extends UserActions
case class MakeSomethingWithUsers extends UserActions
val dao = actor {
case GetUsers() => ...
case GetUser(userId) => ...
case AddUser(user) => ...
}
val service = actor {
case GetUsers() => ...
case GetUser(userId) => ...
case AddUser(user) => ...
case MakeSomethingWithUsers() => ...
}
val controller = actor {
case Get("/users") => ...
case Get("/user", userId) => ...
case Post("/add-user", user) => ...
}
ここでは、Get() および Post() エクストラクタがどのように実装されているかはあまり重要ではないと思います。これを実装するためのフレームワークを作成するとします。次のようにコントローラーにメッセージを送信できます。
controller !! Get("/users")
同じことがコントローラーとサービスによって行われます。この場合、ワークフロー全体が同期します。さらに悪いことに、一度に 1 つの要求しか処理できません (その間、他のすべての要求はコントローラーのメールボックスに届きます)。だから私はそれをすべて非同期にする必要があります。
このセットアップで各処理ステップを非同期に実行するエレガントな方法はありますか?
私が理解している限り、各層は受信したメッセージのコンテキストを何らかの形で保存してから、下の層にメッセージを送信する必要があります。下の層が何らかの結果メッセージで返信する場合、最初のコンテキストを復元し、この結果を元の送信者に返信できるはずです。これは正しいです?
さらに、現時点では、各階層にアクターのインスタンスが 1 つしかありません。それらが非同期で動作する場合でも、1 つのコントローラー、サービス、および dao メッセージのみを並行して処理できます。これは、同じタイプのアクターがもっと必要であることを意味します。これにより、各層の LoadBalancer が表示されます。これは、UserService と ItemService がある場合、両方を別々に LoadBalac する必要があることも意味します。
何か間違ったことを理解しているような気がします。必要な設定はすべて複雑すぎるようです。これについてあなたはどう思いますか?
(PS: DB トランザクションがこの図にどのように適合するかを知ることも非常に興味深いですが、このスレッドではやり過ぎだと思います)