Search code examples
axon

Separating Axon commands and its effects


I'm maintaining an event-sourced application that went far off the road I'm afraid.

In one case a command is received by an aggregate root that publishes an event that is handled by an event handler that needs to do 2 things:

  1. send a command (cmd1) to another aggregate root that will publish an event that will create a number of sagas each firing of some commands that are eventually handled by a number of aggregates
  2. send a second command (cmd2) that will also lead to all sorts of command/event/command sequences.

In schematic form:

cmd0 -> AR0 -> evt0 -> evtHandler -> cmd1 -> AR1 -> evt1 -> saga stuff and more cmds and evts
                                 |-> cmd2 -> AR2 -> evt2 -> more saga stuff, cmds and evts

Everything happens in the same thread and everything happens in 1 transaction started at the first command handling.

Now the goal: all events, saga's, aggregate calls originated from the first command (cmd1) should happen first and then all events, saga's and aggregate calls originated from the second command (cmd2) should happen.

Here's the observation: cmd1 calls AR1 that published evt1 but after that cmd2 calls AR2 publishing evt2. All other events and commands originating from cmd1 are mingled with those from cmd2.

First I thought I could get away with it using the UnitOfWork but even explicitly creating a separate unit of work for handling cmd1 didn't solve the problem. Looking at the implementation in AbstractEventBus I see that the events are simply merged in the parent unit of work and thus end up being merged with the ones originating from cmd2.

Here's the question: Is there a way to first call cmd1 and wait until all effects originating from that command are handled before calling cmd2 while still preserving the transactional atomicity that I currently have?


Solution

  • To be completely honest with you Jan, the best would be if the components within your application don't rely to much on that order. It essentially means you have distinct message handling components, which in essence could be different micro service, but they are all tied together as the order is important.

    Ideally, you'd set up your components to work on their own. So, aggregates handle a command and publish the result, done. Sagas react to events, regardless of where they come from, and react on them with actions (e.g. command dispatching).

    Embracing the eventuality would help here, as it will drop the entire requirement of waiting for one process to complete.

    From a theoretical stance, that would be my response.

    From a more pragmatic corner looking at your question, I'd like to point out that it sounds like a rabbit hole you are going in to. You don't only want cmd1 handling to be done, you want event handling on all sagas to be resolved, including commands coming out of that too, correct? Who's here to tell what the number of Sagas is? Or what the number of commands those saga dispatch need to be taken into account? These criteria will likely change over time, adding more an more stuff which needs to happen "in a single transaction".

    Well, yes there are way to wait for processing from some parts, to pull them all in a single transaction. But to be honest with you, I wouldn't recommend taking that route, as it will only make using such a message based system more and more complex.

    The crux is what all effects are. From the point of dispatching that command, you should only care if that exact command handles successfully yes or no, and that's where the concerns should end.

    I know this does not give you a simple programmatic solution, as you need to adjust the design. But I think decoupling is the only right way to go hear.

    That's my two cents to the situation, hope this helps you further in any way Jan.


    Message Anticipation explanation update

    In essence, the messages you'd use in an Axon application form a boundary. A boundary after which the components essentially don't have a clue what is going to handle those messages. The behaviour per message differs a little, but might clarify what opens you have too:

    • Commands - Commands are consistently routed to a single handle, on a single instance. Furthermore, you can anticipate a response, in the form of an OK or NOK. OK's mean the handler is void or the identifier of a created entity (like the aggregate itself). NOK's typically are the exceptions you throw from your command handling methods, which signal something went wrong or the command simply couldn't be executed and it should be let know to the dispatching end.
    • Events - Events will be broadcast to any component which has subscribed itself to the EventBus as being capable to handle a given event. Note that event handling is segregated in time from the actual publication point of the event. This means there is no way there are results from event handling which could (or should) be returned to the dispatcher of an event.
    • Queries - Query messages can be routed in several forms. Either a single component is best suited to answer the query (called Point-to-Point queries). You can also dispatch a query to several handlers and aggregate the results (called Scatter-Gather queries). Lastly, you can subscribe to query models by doing a "Subscription query", which is essentially a combination of a point-to-point followed up by a Flux of updates. Clearly, query dispatching would mean you are receiving a result from some component. It's just that you have freedom in the type of query you do. If any assurance is required about the "up-to-date"-ness of a query response should be part of the implementation of the query being sent and how it is handled by a @QueryHandler annotated method.

    Hope this provides some additional clarity at what each of the messages do in an Axon application!