I am designing a toy project dealing with the bank account management in F# with Event Sourcing.
I would like to get a bit of review (not really a code review) about my Commands and Events to see if things make sense to someone else.
// Force to use a certain Guid generation function
type BankAccountId = BankAccountId of Guid
// Force to have positive values via a constructor function
type Money = private Money of decimal
type PersonalName = {
FirstName: string;
MiddleInitial: string option;
LastName: string;
}
type AccountInitialization = {
Owner: PersonalName
}
type CashDeposit = {
AccountId: BankAccountId
Amount: Money
}
type CashWithdrawal = {
AccountId: BankAccountId
Amount: Money
}
type Command =
| InitializeAccount of AccountInitialization
| WireMoney of AccountTransaction
| DepositCash of CashDeposit
| WithdrawCash of CashWithdrawal
type AccountInitialized = {
Owner: PersonalName
AccountId: BankAccountId
}
type AccountDebited = {
Amount: Money
From: BankAccountId
To: BankAccountId
}
type AccountCredited = {
Amount: Money
From: BankAccountId
To: BankAccountId
}
type CashDeposited = {
AccountId: BankAccountId
Amount: Money
}
type CashWithdrawn = {
AccountId: BankAccountId
Amount: Money
}
type InvalidOperationAttempted = {
AccountId: BankAccountId
}
type Event =
| AccountInitialized of AccountInitialized
| AccountCredited of AccountCredited
| AccountDebited of AccountDebited
| CashDeposited of CashDeposited
| CashWithdrawn of CashWithdrawn
| InvalidOperationAttempted
First, in this current design, seems there is a lot of redundant information accross different types, I am not sure whether in F# this is a common practice to have a single type for different kind of Event / Command data that are "sort of" related:
For examples about those events below:
type CashWithdrawn = {
AccountId: BankAccountId
Amount: Money
}
type CashDeposited = {
AccountId: BankAccountId
Amount: Money
}
and the commands below:
type CashDeposit = {
AccountId: BankAccountId
Amount: Money
}
type CashWithdrawal = {
AccountId: BankAccountId
Amount: Money
}
Not sure is it better to have something like that for commands:
type CashOperation = {
AccountId: BankAccountId
Amount: Money
}
and like this for events;
type CashOperationOccured = {
AccountId: BankAccountId
Amount: Money
}
or even worse / better?:
type CashOperation = {
AccountId: BankAccountId
Amount: Money
}
for both commands and events.
Pushing that logic a bit forward I could also pretend that wiring money and cash operations are almost the same in the sense that From
To
could be the same bank account, but that sounds a bit too twisted to me.
Also not sure that having two different events (ie. AccountDebited
AccountCredited
) for one command (ie. WireMoney
) is smart, considering that I am storing events, the projection might be double the amount of money at some point. However it can seem legit to have those two events. I am a bit confused.
CreateJournalEntry
. Every command always involves two accounts, and always involves credits and debits in equal amounts overall. A cash deposit or withdrawal involves the relevant person's account and a special cash account which accounts for the total amount of cash on hand.AccountId
is associated with the typeBankAccountId
everywhere.Cat
associated with the typeDog
. It is only in your mind thatAccountId
andBankAccountId
are even remotely related. I had to do a double-take and scroll back up to make sure I didn't miss something when I reached that the first time.Id: Guid
?