Abaks: events
I tend to start with the events whenever I start an event source-based system.
But first, let's recall the requirements:
- I should start a Period with a name (i.e. "June 2023"), start and stop dates, an initial balance
- I can add/change/comment/delete an Entry, which is defined by an amount, a date, and a category
- An Entry can be expected or not
- At the end, I should be able to validate or flag in conflict an entry
Which gives us:
data AbaksEvent
= Started {periodId :: PeriodId, name :: Text, from :: Day, to :: Day, initialBalance :: Amount}
| EntryAdded {entry :: Entry}
| EntryAmountChanged {entryId :: EntryId, amount :: Amount}
| EntryValidated {entryId :: EntryId}
| EntryCommented {entryId :: EntryId, comment :: Text}
| EntryMarkedInConflict {entryId :: EntryId, reason :: Text}
| EntryDeleted {entryId :: EntryId, comment :: Text}
deriving stock (Eq, Show, Generic)
Also with the following supporting types:
newtype PeriodId = PeriodId {getPeriodId :: Int}
deriving stock (Eq, Ord, Show, Generic)
data Entry = Entry
{ entryId :: EntryId,
}
deriving stock (Eq, Show, Generic)
newtype EntryId = EntryId {getEntryId :: Int}
deriving stock (Eq, Ord, Show, Generic)
newtype Amount = Amount {getAmountInCents :: Int}
deriving stock (Eq, Ord, Show, Generic)
data EntryState
= Expected
| Unexpected
| Validated
| InConflict Text
deriving stock (Eq, Show, Generic)
Actually, there's four shortcomings in this design:
EntryAmountChanged
is not explicitly required and might probably be a parameter ofEntryMarkedInConflict
EntryDeleted
is not requiredEntryAdded
takes a fullEntry
, which mean it could be in a 'final'EntryState
Amount
does not distinguish deposits and withdrawals
I would argue that these are 'improvements' made for UX purposes, users can make mistakes, enter the wrong amount, or on the wrong period, etc.
Next time we will see the commands, we'll have a lot to cover since I have a quite opinionated design.