Я разрабатываю следующий eDSL:
import Control.Monad.Free
type CommandHandler
= PersistedCommand -> Maybe AggregateSnapshot -> CommandDirective
data CommandDirective = Reject RejectionReason
| SkipBecauseAlreadyProcessed
| Transact (CommandTransaction ())
type RejectionReason = String
data Action a = PersistEvent Event a
| UpdateSnapshot AggregateSnapshot a
| GetCurrentTime (UTCTime -> a )
| GetNewEventId (EventId -> a) deriving (Functor)
type CommandTransaction a = Free Action a
persistEvent :: Event -> CommandTransaction ()
persistEvent event = Free . PersistEvent event $ Pure ()
updateSnapshot :: AggregateSnapshot -> CommandTransaction ()
updateSnapshot aggregateSnapshot
= Free . UpdateSnapshot aggregateSnapshot $ Pure ()
getNewEventID :: CommandTransaction EventId
getNewEventID = Free $ GetNewEventId Pure
getCurrentTime :: CommandTransaction UTCTime
getCurrentTime = Free $ GetCurrentTime Pure
Я бы хотел, чтобы UpdateSnapshot использовался только один раз в последовательности, а не дважды, как я делаю сейчас. Я мог бы сделать это с фантомными типами, и тогда UpdateSnapshot будет последним элементом цепочки действий. Но это отчасти семантически неправильно и не работает для двух действий с одинаковой природой тогда ...
Например: 2 последнее обновлениеSnapshot не должно быть действительным:
do
now <- getCurrentTime
eventId <- getNewEventID
persistEvent $ WorkspaceCreated
{ eventId = eventId
, createdOn = now
, workspaceId = workspaceId }
updateSnapshot $ AggregateSnapshot
{ lastOffsetConsumed = 0
, commandsProcessed = Set.fromList [commandId]
, state = AggregateState { aggregateId = workspaceId } }
updateSnapshot $ AggregateSnapshot
{ lastOffsetConsumed = 0
, commandsProcessed = Set.fromList [commandId]
, state = AggregateState { aggregateId = workspaceId } }