Creating fat events from within Aggregate

Hi All,

I have a question about creating fat events.

So let’s say we have an event A (e.g. ImageCreatedEvent) and later on we have an event B (e.g ImageTransformedEvent).

In our cases Event B typically has a link to the identifier of Event A to reference to the source. In this case the transformed image refers to the original image.

Now in case we want to create sagas we always have to listen on Event A as the association field that defines its scope is missing in Event B. For example, the ImageCreatedEvent has a project field…and the saga should operate per project.

We already start the saga when we see Event A, and create an association on the reference field to catch Event B.

This makes Sagas much slower and more complex. Especially because sagas are single threaded (and we prefer not to turn them in eventhandlers)

Since Event A is immutable in our case (as the necessary fields will never change later on), we could make FAT Event B … including all/some fields of A in Event B.

In our cases we generate Event B in an aggregate where we don’t have access to the fields of A to create the fat Event. It’s not part of the command, as we get this command from the client side.

What’s the best solution to create a fat Event in this case?

  1. Use a query in the aggregate to fetch the necessary additional fields? (Doesn’t feel like a best practice?)

  2. Can we intercept the command to add extra fields based on querying a read model? It’s more like a CommandEnhancer? (Not sure this is possible with AxonFramework?)

  3. Use an eventhandler to generate a new event C that will use a read model to combine both event data? This is possible. Not sure how we would name this new event? ImageTransformedFatEvent?

  4. Any other solution?

Kind regards
Koen

Hi Koen,

I am not sure if I understand your question and flow.
This is how I have pictured it in my head:

  1. CreateImageCommand->ImageAggregate-> ImageCreatedEvent
  2. ImageCreatedEvent->Saga->TransformImageCommand
  3. TransformImageCommand->TransformedImageAggregate->ImageTransformedEvent

What I do not understand is why do you need this fat event, and why do you think that the case you have at the moment will make Saga slower than usual?

In the scenario, I have just described it is obvious that TransformImageCommand is holding enough data to be included in ImageTransformedEvent if needed.

Please, elaborate on your case, so we can help more.

Best,
Ivan

Hi Ivan,

Thanks for your quick response.

The trigger/command comes from the UI (end user) so its not an automatic reaction of Event B.
So this “transform” command comes in via API, and doesn’t contain enough data.

The Saga might something unrelated to the transformation proces but this SAGA workflow has a starting point when Event B occurs. But to define the association_key, we miss this info … and therefore have to listen to Event A as well.

Not sure this makes the situation a bit more clear?

Is it possible for this “transform” command to include all the data it needs so the aggregate can make a decision immediately (without querying some projection)?

If not, then you can break down the process of handling TransformImageCommand into parts (async/distributed):

  1. TransformImageCommand->TransformedImageAggregate->ImageTransformationInitiatedEvent
  2. ImageTransformationInitiatedEvent->Saga->QueryImageProjection (send Query)
  3. Based on the response -> MarkTransformImageCompletedCommand (or MarkTransformImageNOTCompletedCommand)

In this case, your aggregate B(TransformedImageAggregate) has three states at least: Pending (ImageTransformationInitiatedEvent), COMPLETED (ImageTransformedEvent), NOT_COMPLETED (ImageNotTransformedEvent)

This is a general rule which you can apply in situations when your aggregate has to contact third-party service/component to make some decisions in command handlers. This will decrease runtime dependency and it will be easier to test as well-> your aggregate is not depending on it actually.

In situations where this third-party service is a nearby/local component (spring data repository, for example), you can consider including it in interceptor or command handler directly to do the querying. This is increasing the design and runtime coupling(your aggregate is depending on this component), but the code is maybe simpler…

I hope this makes sense.

Best,
Ivan

Hi Ivan,

Thanks for you reply.

Didn’t think about this option, but we indeed use this for external services.

We might use the simple version as the read model is local.

Question: Can a command interceptor get Command A as input, and generate another type of Command B as output? In my case Command B include the extra fields.

Kind regards

Hi Koen,

I would recommend using this local projection of aggregate A in aggregate B (in interceptor or in command handler directly).

Essentially, this small command side projection is the Query side model of your aggregate A. This projection can be implemented as any other EventHandler that subscribes to events from A and de-normalizing the state in some DB. A Spring Repository interface of this projection could be injected in aggregate B for validation purposes. Please mind that it usually make sense to make this command side projections immediately consistent with the aggregate state (by setting this Event Handler as Subscribing). This is how you can utilize CQRS pattern effectively.

Hopefully, this answers your question

Best