Introduction
Event sourcing is an architecture model based on the storage of the sequence of events that have occurred in an objects' lifetime rather than storage of a point-in-time state of that object.
It lends itself well to financial applications because (typically) any account balance is a result of the events leading to that balance and we want the ability, both to see the underlying derivation of a figure and also to be able to play that valuation as of a given point in time.
Event sourcing allows us to perform “what if” analysis by filtering out events or by playing in alternate streams.
Quick Example – A Simple Bank Account
A bank account is probably the simplest financial stream consisting solely of deposits and withdrawals of a single currency. In this model, an event is simply an amount by which the balance is adjusted – deposits being positive and withdrawals being negative.
[ - - +50 - - +50 - - -100 - ->]
In this example, the valuation changes only when the actual deposit or withdrawal occurs – so playing the event stream through to the end, we can see that the current balance is now 0.
Second Example – A Shareholding Account
For a shareholding account, there are two streams. There is a positions stream where purchases or sales of the shareholding are recorded and there is a pricing stream where prices for the underlying share are recorded.
[ - - +50 - - +50 - - -100 - ->]
[178.1 - - - - 178.0 - - - - 178.9 - - >]
These can be combined into a valuation stream which is the combination of the current holding multiplied by the most recent price. Both a holding event and/or a pricing event will trigger a valuation.
[ 0 - - 8905.00 - - 17810.00 - - 17800.00 - - 0.00 - - 0.00 ]
Third Example – Holdings in a Non-Base Currency
If your shareholdings are held in a non-base currency, then the changes in the exchange rate between the holding currency and the account base currency also affect the valuation.
[ - - +50- - +50- - -100- -> ]
[178.1 - - - - 178.0 - - - - 178.9 -> ]
[1.1- - - 1.2 - - - 1.3 - - - 1.4 -> ]
Events
For any event, you need to know – when did it occur, what stream was it in, what was its value. From the above example, you can also see that the value can be an absolute amount (for example, a price or an exchange rate) or it can be a change (or delta) amount.
For an event stream that is a stream of delta amounts, it can be helpful to write in periodic snapshots of the current value. This is to allow us to play back the stream to any given point by going to the snapshot prior to that point, then playing in any delta events post that snapshot time.
Derived Events
Streams can be source data or derived by functions of one or more source streams. For derived events, a function is used to calculate the value from its constituent streams and this is triggered for any event on any of those streams.
Double-Entry Accounting
In double entry accounting, holding events are recorded in two (or more) streams – one representing each of the ledger accounts the event impacts (most commonly, one for assets and one for liabilities). Traditionally, this is done using posting rules on the input stream to post to the relevant two (or more) account streams.
Snapshots
In order to keep the read-access time to a minimum, the architecture underlying this should support reading and creating snapshots. These snapshots can be treated as data transfer objects for the purpose of integration into any system based on them (such as an MVC application).
The interfaces to define the operations required would look like:
public interface IRepositoryWriteSnapshot<TKey, TEntity> where TEntity : IKeyedEntity<TKey>
{
void RequestSnapshot(TKey key);
void RegenerateFromAsOf(TKey key, long synchronisation);
}
And the read side can be implemented as an extension to the usual repository pattern:
public interface IRepositoryReadSnaphot<TKey, TEntity>
: IRepositoryRead<TKey, TEntity> where TEntity : IKeyedEntity<TKey>
{
bool ExistedAsOf(TKey key, long synchronisation);
Nullable<TEntity> GetByKeyAsOf(TKey key, long synchronisation);
}
In practice, audit or legal requirements may require you to keep old snapshots if they were sent externally. A versioning mechanism would be needed to make sure any display (UI or print) uses the latest version of the snapshot.
Points of Interest
One of the biggest things to consider with this type of architecture is idempotency - making the system immune to error if an event is run more than one time. In practice, this tends to mean replacing messages that perform a delta (such as "reduce the account by $50") with messages that perform an absolute (such as "set the account holding to $745.20").
However rather than artificially making absolute events, you are better to drill down until you get to events that are inherently absolute. In the above example, having absolute deposit and withdrawal events is preferable to having a "set balance to x" derived event.
Another challenge is sequencing - it is important that events are added to the stream in the correct chronological order. In practice, a universally accepted timestamp will need to be used.
History
- 2nd May, 2014: Initial concept