Статьи

Event Sourcing, автоматы Akka и модели функциональных доменов

Ранее я писал о Event Sourcing и функциональных моделях доменов. В этом посте я хотел бы поделиться своими мыслями на ту же тему и о том, как с более высоким уровнем абстракции вы можете сделать вашу совокупную границу домена более устойчивой и отделенной от внешних ссылок.

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

Сохранение совокупности в чистоте

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

Сделка может находиться в различных состояниях, таких как вновь введенная , добавленная дата валютирования , обогащенная информацией о налогах и сборах , вычисленная чистая стоимость сделки и т. Д. В торговом приложении, когда сделка проходит через конвейер обработки, она перемещается из одного состояния в другое. Конечное состояние представляет собой завершенный Торговый объект, который готов для расчетов между контрагентами.

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

Ничего страшного. Но кто несет ответственность за управление этими изменениями состояния и регистрацию графика изменений? Это определенно не входит в совокупность. Агрегат должен быть чистымабстракция. Мы должны спроектировать его как неизменный объект, который может реагировать на события и трансформироваться в новое состояние. Фактически, агрегатная реализация не должна знать, обслуживает ли она архитектуру, основанную на событиях, или нет.

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

sealed abstract class Trade {
  def account: Account
  def instrument: Instrument
  //..
}
 
case class NewTrade(..) extends Trade {
  //..
}
 
case class EnrichedTrade(..) extends Trade {
  //..
}

Другой вариант может состоять в том, чтобы иметь один тип данных для моделирования состояний Trade и модели в виде неизменяемых перечислений с изменениями, внесенными в агрегат в качестве функциональных обновлений. Мутация отсутствует, но используйте функциональные структуры данных, такие как молнии или линзы типа, чтобы создать преобразованный объект в новом состоянии Вот пример, где мы создаем обогащенную сделку из вновь созданной.

// closure that enriches a trade
val enrichTrade: Trade => Trade = {trade =>
  val taxes = for {
    taxFeeIds      <- forTrade // get the tax/fee ids for a trade
    taxFeeValues   <- taxFeeCalculate // calculate tax fee values
  }
  yield(taxFeeIds ° taxFeeValues)
  val t = taxFeeLens.set(trade, taxes(trade))
  netAmountLens.set(t, t.taxFees.map(_.foldl(principal(t))((a, b) => a + b._2)))
}

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

Ввод конечных автоматов

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

В нашем примере Торговая служба может быть смоделирована как FSM, который контролирует жизненный цикл Сделки. В дальнейшем ..

import TradeModel._
 
class TradeLifecycle(trade: Trade, timeout: Duration, log: Option[EventLog])
  extends Actor with FSM[TradeState, Trade] {
  import FSM._
 
  startWith(Created, trade)
 
  when(Created) {
    case Event(e@AddValueDate, data) =>
      log.map(_.appendAsync(data.refNo, Created, Some(data), e))
      val trd = addValueDate(data)
      notifyListeners(trd)
      goto(ValueDateAdded) using trd forMax(timeout)
  }
 
  when(ValueDateAdded) {
    case Event(StateTimeout, _) =>
      stay
 
    case Event(e@EnrichTrade, data) =>
      log.map(_.appendAsync(data.refNo, ValueDateAdded, None,  e))
      val trd = enrichTrade(data)
      notifyListeners(trd)
      goto(Enriched) using trd forMax(timeout)
  }
 
  when(Enriched) {
    case Event(StateTimeout, _) =>
      stay
 
    case Event(e@SendOutContractNote, data) =>
      log.map(_.appendAsync(data.refNo, Enriched, None,  e))
      sender ! data
      stop
  }
 
  initialize
}

Фрагмент выше содержит много других деталей, которые я не успел обрезать. На самом деле это часть реализации торгового приложения, основанного на событиях, которое использует асинхронный обмен сообщениями (актерами) в качестве основы для регистрации событий и обращения к нескольким потребителям на основе парадигмы CQRS.

Обратите внимание, что модель FSM, приведенная выше, очень четко описывает состояния, которых может достичь модель Trade, и события, которые она обрабатывает, находясь в каждом из этих состояний. Также мы можем использовать эту технику FSM для регистрации событий (для получения событий), уведомления слушателей о событиях (CQRS) очень декларативным способом, как реализовано выше.

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

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

 

С http://debasishg.blogspot.com/2012/01/event-sourcing-akka-fsms-and-functional.html