Перенос типичная 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 = { ... }
}

Вы можете найти что-то подобное во многих приложениях Spring. Мы можем взять простую реализацию, которая не имеет общего состояния, потому что не имеет синхронизированных блоков ... так что все состояние находится в базе данных, а приложение зависит от транзакций. Сервис, контроллер и дао имеют только один экземпляр. Таким образом, для каждого запроса сервер приложений будет использовать отдельный поток, но потоки не будут блокировать друг друга (но будут заблокированы 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")

То же самое может сделать диспетчер и служба. В этом случае весь рабочий процесс будет синхронным. Хуже того - я могу обрабатывать только один запрос за раз (тем временем все остальные запросы будут попадать в почтовый ящик контроллера). Поэтому мне нужно сделать все асинхронным.

Есть ли какой-нибудь элегантный способ асинхронно выполнять каждый этап обработки в этой настройке?

Насколько я понимаю, каждый уровень должен каким-то образом сохранять контекст сообщения, которое он получает, а затем отправлять сообщение на уровень ниже. Когда уровень ниже отвечает с некоторым сообщением о результате, я смогу восстановить исходный контекст и ответить с этим результатом исходному отправителю. Это правильно?

Более того, на данный момент у меня есть только один экземпляр актера для каждого уровня. Даже если они будут работать асинхронно, я все равно могу параллельно обрабатывать только один контроллер, сервис и сообщение дао. Это означает, что мне нужно больше актеров одного типа. Это приводит меня к LoadBalancer для каждого уровня. Это также означает, что если у меня есть UserService и ItemService, я должен LoadBalace их обоих по отдельности.

Я чувствую, что что-то не так. Вся необходимая конфигурация кажется слишком сложной. Что вы думаете об этом?

(PS: Было бы также очень интересно узнать, как транзакции БД вписываются в эту картину, но я думаю, что это излишне для этой темы)

21
задан Arjan Tijms 15 June 2013 в 12:51
поделиться