Написание приложений с актерами Scala на практике II
Поскольку мой первый вопрос был очень длинным, я задаю его как отдельный вопрос. Это еще один вопрос об архитектуре актерского приложения.
Отслеживание путей сообщения через приложение
Давайте возьмем кусок кода Java:
public void deleteTrades(User user, Date date) {
PermissionSet ps = permissionService.findPermissions(user)
if (ps.hasPermission("delete")) {
Set<Trade> ts = peristence.findTrades(date);
reportService.sendCancelReports(ts);
positionService.updateWithDeletedTrades(ts);
}
}
В этом коде у меня есть 4 отдельных компонента, и взаимодействие между ними требуется для процедуры deleteTrades
четко определен. Это полностью содержится в методе deleteTrades
,
Моделирование этого с Actor
s и заменив мои 4 компонента на 4 отдельных участника, как мне отслеживать (по моему мнению), что включает в себя процедура? Особенно если я избегаю использования !?
оператор, то вполне вероятно, что я буду отправлять сообщение ConditionalDelete
к моему PermissionActor
, который будет отправлять сообщение GetTradesAndDelete
к моему PersistenceActor
которые затем будут отправлять дальнейшие сообщения и т. д. и т. д. Код для обработки удаления будет разбросан по моему приложению.
Это также означает, что почти каждому актору нужен дескриптор любого другого актера (для пересылки сообщений).
Как и в моем предыдущем вопросе, как люди справляются с этим? Есть хороший инструмент моделирования, который позволяет вам отслеживать все это? Люди используют !?
Я превращаю слишком много компонентов в Actor
s?
2 ответа
Вы используете 5 компонентов, безусловно. Есть актеры, занимающиеся конкретными задачами, а также есть оркестровщик.
Вопрос, который вы должны задать, конечно, заключается в том, как вы цепляетесь в асинхронном режиме. Ну, на самом деле это довольно просто, но может затенить код. По сути, вы отправляете каждому компоненту нужный вам ответ.
react {
case DeleteTrades(user,dates) =>
PermissionService ! FindPermissions(user, DeleteTradesPermissions(dates) _)
case DeleteTradesPermissions(dates)(ps) =>
if (ps hasPermission "delete")
Persistence ! FindTrades(date, DeleteTradesTradeSet _)
case DeleteTradesTradeSet(ts) =>
ReportService ! SendCancelReports(ts)
PositionService ! UpdateWithDeletedTrades(ts)
}
Здесь мы используем карри, чтобы передать "даты" в первом ответе. Если с взаимодействием связано много параметров, возможно, лучше хранить информацию обо всех текущих транзакциях в локальном HashSet и просто передавать токен, который вы будете использовать для поиска этой информации при получении ответа.
Обратите внимание, что этот единственный субъект может обрабатывать несколько одновременных действий. В этом конкретном случае просто удалите транзакции, но вы можете добавить любое количество различных действий для его обработки. Когда данные, необходимые для одного действия, готовы, то это действие продолжается.
РЕДАКТИРОВАТЬ
Вот рабочий пример того, как эти классы могут быть определены:
class Date
class User
class PermissionSet
abstract class Message
case class DeleteTradesPermission(date: Date)(ps: PermissionSet) extends Message
case class FindPermissions(u: User, r: (PermissionSet) => Message) extends Message
FindPermissions(new User, DeleteTradesPermission(new Date) _)
Несколько пояснений о карри и функциях. Класс DeleteTradesPermission
карри, так что вы можете передать Date
на нем, и есть какая-то другая функция завершить его с PermissionSet
, Это будет образец ответных сообщений.
Теперь класс FindPermissions
получает в качестве второго параметра функцию. Актер, получивший это сообщение, передаст возвращаемое значение этой функции и получит Message
быть отправленным в качестве ответа. В этом примере сообщение будет содержать Date
, который отправил вызывающий актер, и PermissionSet
, который обеспечивает отвечающий актер.
Если ответа не ожидается, например, в случае DeleteTrades
, SendCancelReports
а также UpdateWithDeletedTrades
в этом примере вам не нужно передавать функцию возвращаемого сообщения.
Так как мы ожидаем функцию, которая возвращает Message в качестве параметра для тех сообщений, которые требуют ответа, мы могли бы определить такие черты, как это:
trait MessageResponse1[-T1] extends Function1[T1, Message]
trait MessageResponse2[-T1, -T2] extends Function2[T1, T2, Message]
...
Актеры не должны использоваться для замены традиционных сервисных компонентов без рассмотрения.
Большинство сервисных компонентов, которые мы сейчас пишем, путем обучения, не имеют состояния. Компонентами службы без сохранения состояния легче управлять (без класса сообщений и т. Д.), Чем с участниками. Однако одной из вещей, которой им не хватает, по сравнению с актерами, является асинхронное выполнение. Но когда клиенты ожидают, что результаты будут возвращаться синхронно большую часть времени, синхронные компоненты службы без сохранения состояния подойдут для этой задачи.
Актер хорошо подходит, когда нужно управлять внутренними состояниями. Нет необходимости выполнять какую-либо синхронизацию внутри "act()", чтобы получить доступ к внутренним состояниям и беспокоиться о состоянии гонки. Пока "!?" не используется внутри "act()", взаимная блокировка также должна быть минимизирована.
Актеры должны с осторожностью относиться к любой обработке блокировки, выполняемой при обработке сообщений. Поскольку субъекты обрабатывают свои сообщения последовательно, каждый раз, когда они блокируются в ожидании ввода-вывода внутри "act()", они не могут обрабатывать любые другие сообщения в своих почтовых ящиках. Используемая здесь идиома Scala - запуск другого специального актера, который выполняет фактическую операцию блокировки. Этот анти-шаблон влияет на актера, основанного на событиях (реакции), еще больше, потому что он блокирует поток, в котором актер на основе событий также поддерживается.
Насколько я могу судить, все четыре ваших вызова сервисных компонентов потенциально блокируются, поэтому следует позаботиться о том, чтобы их масштабировать при преобразовании в актеров.