6

I'm currently teaching myself event sourcing and have got the concept enough to start developing a dummy app in C# and EventStore. My app is an easy-to-understand bank account system.

If we model a bank account as a series of withdrawal and deposit events then we can easily calculate the account's current balance by adding up all the deposits and subtracting all the withdrawals, and EventStore gives us a way to implement this in the data store using projections.

However, if we have a business rule that dictates that accounts can never go overdrawn (i.e. we must reject requests to withdraw from an account if the current balance is insufficient to cover the requested funds), then our BankAccount entity also needs to know its current balance.

Again, this is easy to calculate by running over the series of bank account events but I have a deep unease with the fact that I have had to implement my projection of "balance" twice: once in the data store and once in the domain.

Is this expected from Event Sourcing, and I shouldn't worry about it? Or am I going down the wrong path, and in fact the balance should not be calculated in the domain code and should instead be retrieved from the data store every time it is required? This means that a round trip to the data store would be required every time an entity is modified.

I've seen in Greg Young's talks that it's supposed to be a natural fit for DDD so there shouldn't be an incompatibility there.

If I've misunderstood any part of this set up then any help would be greatly appreciated.

  • Event Sourcing is most commonly used in the context of the CQRS pattern, where the entire point is to enable you to have multiple different representations of the same data (i.e. different representations are optimized for different purposes, e.g. one for long term storage, one for fast queries to commonly accessed data, one for recent updates ...). By making the event store the single source of truth, the theory is that you no longer have to worry about having multiple alternative sources, because they all refer back to the event store in predictable, testable ways. – Jules Feb 16 '18 at 17:10
  • Another question, though: why do you need to store the balance in the data store? What happens if you calculate it on demand from the event history, and just cache that value in the in-memory representation? The useful thing about having an event store is that you can experiment with such questions, and change your mind about your answer to them at any time. – Jules Feb 16 '18 at 17:14
  • 1
    @Jules I'm attempting to think in a CQRS mindset and make the balance retrievable for anyone who wants it without having to load the domain, i.e. having a separate read model. – Richiban Feb 16 '18 at 17:17
6

Your event log is your only source of truth. You cannot trust the data in the projections, because it is eventually consistent and does not have to be up-to-date. As you have mentioned, you can indeed get the latest state of your domain entity by reading all events related to that one specific entity and flatting them to your final representation.

So if an account can never be overdrawn, you need to know its balance, you need to calculate it in your domain object from the event stream. This is not only recommended but necessary.

Of course, if your projections are as simple as being the latest (eventually consistent) state representation of your entities, it might seem you are duplicating logic. However, the latest state of an entity is in most cases not the only representation of the entity, i.e. how you'd like to view it the data.

For different purposes you may (and with large systems are likely to) have different projections. Some can be entities, but other projections may be entire aggregations. With these aggregations you could then use an MoneyWasDeposited event from all BankAccount entities to update a monthly cashflow to see how much money is actually flowing through your system.

The strength of projections comes at the time when you find out you need different representations of - internally - the same data. Thanks to projection pre-building, increasing complexity of the view does not slow down load times for end users.


To followup on VoiceOfUnreason's answer, while e.g. sharing the logic for balance calculation makes sense in terms of DRY principle (so that you don't repeat yourself), when actually practicing ES, I have learned it mostly does not.

An event might look like this:

class MoneyWasDeposited: IEvent
{
    public BankAccountId ToBankAccountId;
    public Money Amount;
}

An event sourced aggregate then usually processed this event using a similar mechanism to this:

class BankAccount
{
    private BankAccountId id;
    private Money currentBalance;

    public void ReplayFromEvents(List<IEvent> events)
    {
        for (var event in Events)
        {
            ApplyEvent(event);
        }
    }

    private void ApplyEvent(IEvent event)
    {
        if (event is MoneyWasDeposited)
        {
            currentBalance = currentBalance.Add(event.Amount);
        }
    }
}

while your projection is actually likely to look something like this:

class BankAccountProjection
{
    private MySqlConnection database;

    public void HandleMoneyWasDeposited(MoneyWasDeposited event)
    {
        var statement = database.Prepare("
            UPDATE
                bank_accounts
            SET balance = balance + :depositedAmount
            WHERE id = :id
        ");

        statement.BindValue("depositedAmount", event.Amount.Value);
        statement.BindValue("id", event.ToBankAccountId.ToString());

        statement.ExecuteNoResult();
    }
}

So while your aggregate calculates the value in code from the event, the projection usually applies the change directly to your read-model store and let's the storage engine take care of the data modification.

As you can see, there's really no way to reduce duplication with this anymore, because the aggregate and projection use vastly different mechanisms to achieve the desired result.

Now, I am not saying you couldn't pull out the entire projection from the read-model store, update the read-model in memory and dump it back for update, but it's usually not the way how it's done, as it adds one unnecessary SELECT and achieves virtually the same result anyway.


You may ask: The balance calculation logic is not in a single place. So what if someone, by accident, makes the logic in the projection incorrect?

Consider the scenario that somebody wrote a minus sign to the projections' balance update when money was deposited. This would mean that every time you deposited money to your account, you would suddenly have less.

You would then do the following steps:

  • change the minus sign to a plus sign in the projection method,
  • push the change immediately to production, so that all changes from now on are ok,
  • create a new projection index in your read-model store, e.g. bank_accounts_fixed,
  • run another instance of bank account projection manager from the inception of your system and insert data into bank_accounts_fixed instead,
  • after the local projection finishes running, swap configuration on production to use the bank_accounts_fixed data in place of the bank_accounts_fixed,
  • delete the original bank_accounts index.

And all your user's will suddenly see correct balances. You can do this because you know that the data in your event store is guaranteed to be correct (it should be).

1

Again, this is easy to calculate by running over the series of bank account events but I have a deep unease with the fact that I have had to implement my projection of "balance" twice: once in the data store and once in the domain.

Typically, you would capture the idea that deposits and withdrawals add up to balance in one place, and invoke that code from other parts of the domain, as unnecessary.

If you are trying to use a projection framework, sharing a single implementation may be awkward.

Deposits, Withdrawals, and Balance are part of the description of the data in your domain; they aren't supposed to be some great secret known only to one part of the domain or another.

What to do when a withdrawal input arrives that would put the Balance into a negative state is encapsulated in the domain logic; it might allow the balance to go negative, reject the input, store the input in some other location pending clarification, or accept the input and also flag the account as in violation of the overdraft policy (banks like to collect fees, remember).

Side note: you need to be very careful in modeling something like banking to be thinking about whether or not you can veto a particular message. An ATM machine might decline to make a payout when it thinks that doing so would violate the business rules, but if an ATM has made a payout that you wish it hadn't, you probably don't get to just throw the data away.

I am not asking about the particulars of command failure but instead where the balance calculation should be implemented: domain, data store, or both?

It's state. It might be implicit (you calculated the balance as you apply the events that track each deposit and withdrawal), or explicit (you include a representation of the "current" balance in the deposit/withdrawal events, and apply it).

The domain code is going to need to be able to access it, if the current value is going to be used in the logic of handling a command. Your read models are going to need it if the data is visible to downstream consumers (including rendering views for human operators).

So yes, you might end up duplicating the calculation -- for instance, if your domain model is implemented in one language, but your projections are implemented in a different language.

In the most general case, the aggregate may shift through several states (emit several events) when processing a single command. When that happens, the model will usually need to be able to calculate the intermediate states without the help of the data store. So the logic required to compute the new states will be invoked both before the events are written to the store, and also after.

  • Thanks for your answer @VoiceOfUnreason, but I feel I may need to clarify my question: I am not asking about the particulars of command failure but instead where the balance calculation should be implemented: domain, data store, or both? – Richiban Feb 16 '18 at 21:38
0

I don't know EventStore specifically, but it gives you projections as folds, which is cool.

However, the computation of balance is still logically owned by the core business/domain logic, so just make sure that the domain model gets to specify the projection.

Your domain model can also offer a get account balance operation, which in turn uses the projection.

(If you want to use ES in a more blended manner with CQRS, you can have a fully up-to-date normalized domain write model that holds accounts current balance — that data store can thrown away and be rebuilt from ES at any time, and, the get account balance implementation could use the cached content from this write model.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.