0

I try to bring Event Sourcing to my project but not sure how to do a specific thing in a less errorful way.

This is an "accounting" system but the main focus is on transactions, not on accounts. The thing is that I receive such events as withdrawal or deposit was made, authorization hold was created/decreased/increased/expired/reversed, authorization hold was settled (it means that there will soon be an event for withdrawal), and event with transaction details.

In my case aggregate root is a transaction. It could be created (via authorization hold or directly with withdrawal/deposit events) and updated.

Ane here what I can not fully understand. I get the very first event with authorization hold created. Inside this event, I have only the amount and key for this authorization hold. The key comes from the external system, oviusly. So, I generated a new TransactionId with UUID4, make a new aggregate root, add the first event, and persist it. Next, I receive an event withdrawal was created. This event has amount, transaction key some other data, and may have a key with authorization hold reference in the external system. If it does not have such a key then I simply generate a new TransactionId and store a new aggregate. But if there is such a key then I need to load previous aggregate which was created during the authorization hold created event.

The problem is that withdrawal events do not have aggregate root id from my system. Correct me if I'm wrong but I consider the following flow

  • check if the withdrawal created event has reference to an authorization hold
  • if there the reference is not in the event
    • create a new Transaction aggregate with withdrawal created event
  • if the event has the reference
    • find the authorization hold created event which has this value in its payload
    • get an aggregate id for the founded event
    • load all events for this aggregated id and build the aggregated root
    • add withdrawal created to the aggregated and persist it

Is it correct?

To make the question more abstract and generic. If I do not get aggregate root id in incoming messages then is it ok/valid to search for specific aggregate root id in an event store by some criteria which I build on the values of incoming data?

pepper
  • 1,732
  • 2
  • 17
  • 33

2 Answers2

1

You can't/shouldn't query the event store for some criteria from your aggregates, instead the client should first query the read-side to see if related aggregates exists or not and then add those ID's to the command to the aggregates.

Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • Does it mean that every event that I receive should have an aggregate root id for which this event should be applied? Or some data that could be converted to aggregate id? In my case, it's not possible to have an aggregate id for transactions but each message will have an account number. So, I have to use an account as an aggregate? – pepper Apr 21 '21 at 16:33
  • 1
    You typically send commands to your aggregates and the aggregates then publish 0,1 or more events as a result. The command it sent to a specific aggregate (AggregateID) and if it does not already exist, it will be created. Its the clients jobb to populate the command object with the necessary ID's that the aggregate need from the comnand.,... your example is a bit hard to follow, so I don't 100% fully understand what you try to do. – Tore Nestenius Apr 22 '21 at 06:44
  • My problem is that I'm not fully sure how to handle commands for updating the existing aggregate. So, I have a command to create a transaction. That's fine. But then I will have another command to update the transaction. But since this "command" comes from the external system which does not know anything about aggregate id - they do not send it. The only ID that I can extract from the original even is the account number. Does it sound valid if I use it to build my internal aggregate id - namespaced UUID5. Instead of totally random UUID4. – pepper Apr 22 '21 at 08:10
  • 1
    Of course you can't just send a random command, you need the context and ID's! . So, you first query the read-side. your client application will probably present to the user a "Edit order" page. To display this page, the user-interface will have queried the read-side for all the order-information. Of course the ID of the order is also present here. Then on that page , there is a button "RemoveItemFromOrder" , so the user interface will have all the Id that the command needs. – Tore Nestenius Apr 22 '21 at 09:08
  • Feel free to accept the answer if it answered your question – Tore Nestenius Apr 29 '21 at 07:02
1

As we see the concept of an "external system", you definitely come to the integration area. Normally, internal bounded contexts don't want to be coupled to external systems. Your example shows this issue quite well. You have ids from the external system, which you need to map to your internal ids when processing those external events, and producing internal commands accordingly.

This job is done in a place we call the "Anti-Corruption Layer" (a pattern from Blue Book). It needs to keep all the mappings between external system ids and your internal ids.

In the simplest case, the ACL is just a read model of sorts. For example, when you receive authorization hold created and generate the new id for the hold, you store the mapping somewhere, then use this storage to query the mapped id when you receive the withdrawal event.

However, if this external system is a well-known concept in your context (and it might be), you might want to include those external ids in your domain model, and name them like externalXXXId. Then again, by projecting those initial events to some database as key-value pairs, you'd be able to find this map when you receive the second event and then load your aggregate accordingly (or create a new one).

Alexey Zimarev
  • 17,944
  • 2
  • 55
  • 83