Abaks: Use Cases
After introducing Command
s as our Entites/Core Domain, we then have to introduce next layer: Use Cases.
For reference, it is the place where application-specific wiring.
They aim to be really simple, most of them will just wrap Event sourcing mechanics:
addEntry periodId entry =
runCommand (Entities.addEntry entry) periodId.getPeriodId $
return . bimap (.getExplainedError) (const ())
While some of them will inject some value (or connect various sources):
createPeriod name from to balance = do
periodId <- Entities.PeriodId . AggregateId . UUID.toText <$> embedFinal UUID.nextRandom
runCommand (Entities.startPeriod periodId name from to balance) periodId.getPeriodId $
return . bimap (.getExplainedError) (const periodId)
In the meantime, we have created a supporting function:
runCommand handler aggregateId f =
withEvents aggregateId $ \initialEvents ->
let result = applyCommand handler initialEvents
in (,) (either mempty id result) <$> f result
And a dedicated effect:
data EventSourceEffect (eventType :: Type) (m :: Type -> Type) (a :: Type) where
WithEvents :: AggregateId -> (Events eventType -> m (Events eventType, a)) -> EventSourceEffect eventType m a
We also provide two implementations:
- A pure in-memory,
State
-based - A file-based, backed by a
MVar
, relying onAeson
(which obviously won't scale, or even be maintainable, but good enough for the moment)
All based on a simple type: Map.Map AggregateId (Events eventType)