这个问题困扰了我一段时间(我希望我不是唯一一个)。我想以一个典型的 3 层 Java EE 应用程序为例,看看它看起来如何像使用 actor 实现的一样。我想知道进行这样的过渡是否真的有意义,如果有意义的话,我如何从中受益(也许是性能、更好的架构、可扩展性、可维护性等......)。
下面是典型的Controller(展示)、Service(业务逻辑)、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 = { ... }
}
您可以在许多 Spring 应用程序中找到类似的内容。我们可以采用没有任何共享状态的简单实现,这是因为没有同步块......所以所有状态都在数据库中,应用程序依赖于事务。Service、controller 和 dao 只有一个实例。所以对于每一个请求,应用服务器都会使用单独的线程,但是线程之间不会互相阻塞(但是会被 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")
控制器和服务也会做同样的事情。在这种情况下,整个工作流程将是同步的。更糟糕的是——我一次只能处理一个请求(同时所有其他请求都会进入控制器的邮箱)。所以我需要让它全部异步。
在此设置中是否有任何优雅的方法可以异步执行每个处理步骤?
据我了解,每一层都应该以某种方式保存它接收到的消息的上下文,然后将消息发送到下面的层。当下面的层回复一些结果消息时,我应该能够恢复初始上下文并将这个结果回复给原始发件人。这个对吗?
此外,目前我每层只有一个演员实例。即使它们异步工作,我仍然只能并行处理一个控制器、服务和 dao 消息。这意味着我需要更多相同类型的演员。这将我引向每一层的 LoadBalancer。这也意味着,如果我有 UserService 和 ItemService 我应该分别 LoadBalace 两者。
我有感觉,我理解错了。所有需要的配置似乎都过于复杂了。你怎么看待这件事?
(PS:知道数据库事务如何适应这张图片也很有趣,但我认为这对这个线程来说有点矫枉过正)