Abaks: Views
The last part, in order to make our API is to be able to query it, that's why we have to introduce Views.
Since our project is rather simple, we have made opinionated design choices in our CQRS implementation, we need a View Model, but instead of having Materialized Views or Projections, we'll based our implementation directly on the Events.
Noticeably, we want to be able to get the Period:
data Period = Period
{ periodId :: PeriodId,
}
We could then define a View:
type View a r = Events a -> r
viewPeriod =
withStartedEvent $ \started ->
foldl' go $
Period
{ periodId = started.periodId,
name = started.name,
from = started.from,
to = started.to,
balance = started.initialBalance,
initialBalance = started.initialBalance,
entries = mempty
}
where
go acc =
\case
Started _ -> acc
EntryAdded e ->
adjustAmount (Amount 0) e.entry.amount $
withEntries acc $
Map.insert e.entry.entryId e.entry
EntryAmountChanged e ->
adjustAmount (maybe (Amount 0) (.amount) $ Map.lookup e.entryId acc.entries) e.amount $
withEntry acc e.entryId $ \entry ->
entry {amount = e.amount}
EntryValidated e ->
withEntry acc e.entryId $ \entry ->
entry {state = Validated}
EntryCommented e ->
withEntry acc e.entryId $ \entry ->
entry {comment = e.comment}
EntryMarkedInConflict e ->
withEntry acc e.entryId $ \entry ->
entry {state = InConflict e.reason}
EntryDeleted e ->
withEntries acc $
Map.delete e.entryId
withEntries period f = period {entries = f period.entries}
withEntry period entryId f = withEntries period $ Map.adjust f entryId
adjustAmount previous new period =
period
{ balance = Amount $ period.balance.getAmountInCents - previous.getAmountInCents + new.getAmountInCents
}
Clearly, than a lot of code, let's break this down:
withStartedEvent
splits the stream, extractingStarted
- Then we
fold
over thenEvent
s withPeriod
initialized withStarted
's data - For each
Event
we have to updatePeriod
The great thing with this approach is the freedom we have to define per-query independent piece of code, having a fine-grain interpretation of each Event
, meaning, the precision of our Views
is bound to the precision of our Event
s.