Статьи

Мышление асинхронное — моделирование предметной области с использованием транзиторов Akka — часть 1


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

Репозиторий, как говорит Эрик, является доменно-ориентированной абстракцией поверх вашего уровня хранения данных. Это возвращает вашей модели ощущение, что вы имеете дело с концепциями предметной области, а не сортируете данные по уровням хранения. Как правило, у вас есть контракты для хранилищ на уровне совокупного корня. Базовая реализация связывается с платформой (например, JPA) и гарантирует, что ваш граф объектов совокупного корня будет находиться в покое в реляционной базе данных. Это не должна быть реляционная база данных — это может быть файловая система, это может быть база данных NoSQL. Это сила абстракции, которую репозитории добавляют в вашу модель.

С тех пор, как я начал играть с Эрлангом, я думал о том, чтобы сделать хранилища асинхронными. Я написал некоторые из моих мыслей в этом
постеи даже реализовал прототип с использованием актеров Scala.

Введите
Akka и его облегченную модель актера, которая предлагает поддержку транзакций через STM. Akka предлагает полную интеграцию с различными механизмами персистентности, такими как Cassandra, MongoDB и Redis. Он планирует добавить в этот список и множество реляционных магазинов. Богатство стека Akka дает веские основания для разработки красивой асинхронной абстракции репозитория.

Рассмотрим очень простую модель домена для банковского счета.

case class Account(no: String, 
  name: String, 
  dateOfOpening: Date, 
  dateOfClose: Option[Date],
  balance: Float)

Мы можем смоделировать типичные операции с банковским счетом, такие как открытие нового счета, запрос остатка на счете, размещение суммы на счете через отправку сообщения. Типичные сообщения в Scala будут выглядеть следующим образом.

sealed trait AccountEvent
case class Open(from: String, no: String, name: String) extends AccountEvent
case class New(account: Account) extends AccountEvent
case class Balance(from: String, no: String) extends AccountEvent
case class Post(from: String, no: String, amount: Float) extends AccountEvent

Обратите внимание, что все сообщения являются неизменяемыми объектами Scala, которые будут отправлены клиентом, перехвачены службой домена, которая может при необходимости выполнить некоторую обработку и проверку, а затем, наконец, отправлены в хранилище.

В этой статье мы рассмотрим последний этап жизненного цикла сообщения, то есть то, как оно обрабатывается репозиторием. В следующем посте мы интегрируем все вместе с абстракцией для доменного сервиса. По пути мы увидим многие из достоинств, которые предлагают транзиторы Akka, включая поддержку отказоустойчивой обработки в случае сбоев системы.

trait AccountRepository extends Actor

class RedisAccountRepository extends AccountRepository {
lifeCycle = Some(LifeCycle(Permanent))
val STORAGE_ID = "account.storage"

// actual persistent store
private var accounts = atomic { RedisStorage.getMap(STORAGE_ID) }

def receive = {
case New(a) =>
atomic {
accounts.+=(a.no.getBytes, toByteArray[Account](a))
}

case Balance(from, no) =>
val b = atomic { accounts.get(no.getBytes) }
b match {
case None => reply(None)
case Some(a) =>
val acc = fromByteArray[Account](a).asInstanceOf[Account]
reply(Some(acc.balance))
}

//.. other message handlers
}

override def postRestart(reason: Throwable) = {
accounts = RedisStorage.getMap(STORAGE_ID)
}
}

Приведенный выше фрагмент кода реализует абстракцию репозитория на основе сообщений с базовой реализацией в
Redis . Redis — это расширенное хранилище ключей / значений, которое обеспечивает постоянство для набора структур данных, таких как списки, наборы, хэши и многое другое. Akka предлагает прозрачное постоянство хранилищу Redis через общий набор абстракций. В приведенном выше коде вы можете изменить RedisStorage.getMap (STORAGE_ID) на CassandraStorage.getMap (..) и переключить базовое хранилище на Cassandra.

Вышеупомянутый репозиторий работает через асинхронную передачу сообщений, смоделированную актерами Akka. Вот некоторые из важных моментов в реализации.

  1. Akka is based on the let-it-crash philosophy. You can design supervisor hierarchies that will be responsible for controlling the lifecycles of your actors. In the Actor abstraction you can configure how you would like to handle a crash. LifeCycle(Permanent) means that the actor will always be restarted by the supervisor in the event of a crash. It can also be Lifecycle(Temporary), which means that it will not be restarted and will be shut down using the shutdown hook that you provide. In our case we make the Repository resilient to crashes.
  2. accounts is the handle to a Map that gets persisted in Redis. Here we store all accounts that the clients open hashed by the account number. Have a look at the New message handler in the implementation.
  3. With Akka you can also provide a restart hook when you repository crashes and gets restarted automatically by the supervisor. postRestart is the hook where we re-initialize the Map structure.
  4. Akka uses multiverse, a Java based STM implementation for transaction handling. In the code mark your transactions using atomic {} and the underlying STM will take care of the rest. Instead of atomic, you can also use monadic for-comprehensions for annotating your transaction blocks. Have a look at Akka documentation for details.

Реализации на основе асинхронных сообщений отделяют конечные точки, не блокируют и предлагают больше управляемости при распределении вашей системы. Типичные реализации актерских моделей очень легкие. Актеры Akka занимают около 600 байт, что означает, что вы можете иметь миллионы актеров даже в обычной машине. Akka поддерживает различные типы семантики передачи сообщений, которые вы можете использовать для организации взаимодействия между вашими взаимодействующими объектами.

В следующем посте мы увидим, как репозиторий взаимодействует с доменными службами для реализации обработки клиентских запросов. И да, вы поняли это правильно — мы будем использовать больше актеров Akka.