Maintaining command-side invariants and understanding "transactions"

Context

We have:

  • Model Invariant: one aggregate can only exist if another aggregate does.
  • (Silly) Example: Pet cannot exist without an Owner
    • obviously nonsense but a simple easily understandable example.
  • So you need to include the Owner’s aggregate ID in the CreatePetCommand.
  • And to uphold the invariant the Pet needs to check that the aggregate ID of the Owner corresponds to a known instance.
    • This can be done in the Pet aggregate itself or a separate command handling component.

This is clearly a command-side invariant so it seems like it is another instance of the set based validation problem which we have already discussed on the forum a number of times (see here).

Nevertheless, I wanted to use this example as an illustration to ask some more questions and hopefully clarify my understanding of the Framework.


Potential Solution

  1. Create a subscribing event processor that listens to the OwnerCreatedEvent and maintains a table in a relational database which holds all of the aggregate IDs of the owner.
  2. Create a repository to retrieve entries from the table: OwnerIdRepository.
  3. When the CreatePetCommand is received also inject the OwnerIdRepository into the @CommandHandler and lookup whether the proposed pet owner ID is in the list.
    • If it is: create the pet (because the owner ID is valid).
    • If it is not: throw exception.

Questions

Where does the “transaction” begin and end in (1) above? What exactly is atomic and what is not?

  • Say the OwnerCreatedEvent is raised and the Subscribing event processor fails.
    • Questions:
      • Is the OwnerCreatedEvent still persisted in Axon Server?
      • I am assuming it is not but wondered how this mechanism works. Is it something to do with the Unit of Work?
  • Say we are in the CreateOwner @CommandHandler method and it actually raises more than one event (OwnerCreatedEvent, SomeDeadlineSetEvent)
    • Questions:
      • How does the order of execution work here?
      • Is the subscribing event processor invoked directly after the apply(OwnerCreatedEvent) or after the whole of the body of the @CommandHandler?
  • Say we now have another subscribing event processor listening for the SomeDeadlineSetEvent.
    • an OwnerCreatedEvent is raised and the first Subscribing event processor succeeds and the entry is placed in the OwnerIdRepository successfully.
    • But now the second subscribing processor fails.
    • Questions:
      • Will this result in the rollback of the item in the OwnerIdRepository? Or was the item actually never fully committed and actually was waiting for the final push to commit.

Essentially my overall line of questioning is to try to understand how the idea of a traditional atomic “transaction” maps to maintaining invariants in the command model using the Framework and Axon Server.

Final Question

Has the feature of denoting that you want your event listening class to be a subscribing event processor in the ProcessingGroup annotation been considered?

Having it defining in the properties file works but I think it would make things much more explicit to have it in the .java source file itself directly.


Thanks for reading, I look forward to your responses :slight_smile:

2 Likes

That’s a lot of questions there @vab2048 :wink: Honestly, I’d be interested to hear the answers to some of them myself.

Meanwhile, sticking to your example case, wouldn’t it be more natural to have the Owner aggregate handle the CreatePetCommand and create the Pet aggregate? That way, you know for sure there is an owner, and it better reflects the actual domain IMHO. Now owners get pets vs. pets pop out of nowhere and must assure they have owners.

Of course, you would probably need to handle the situation where a pet changes owner or has more than one owner, or (god forbid) becomes homeless. But all those seem to be operations that belong to the Owner aggregate and not the Pet one. If more than one owner is involved, you may need a Saga to coordinate the process.

I understand the example you used is just for scenario illustrating purpose. Thus, I don’t know how the above translates to the actual domain. But I hope it gives you some food for thought.

1 Like

Thank you for your feedback Milen.

Unfortunately in my case it does not directly translate to my actual domain.

But I am happy you responded anyway because I had not actually ever considered an aggregate handling the command to create another aggregate. It is quite a good domain modelling tool - thank you for the suggestion - it definitely gave me food for thought :slight_smile:

I thin I can answer one of your questions:

Say the OwnerCreatedEvent is raised and the Subscribing event processor fails.

  • Questions:
    • Is the OwnerCreatedEvent still persisted in Axon Server?

Yes, every event is stored and if something goes wrong with event derlivery (e.g. because the recipient throws and exception) then the Axon Server tries to redeliver it several times before it gives it up. This attempts are repeated every time you start your service which registers the event handler. Be aware that your handler logic shoud not make use of exceptions for catching unexpected event content.

Here are my thoughts about your example.

In general, I feel you logically couple the two issues (owner and pet) too closely in your concept. If you conceptually relax the coupling then you will see that things get easier to implement and maintain. So consider the following concept:

  • You have a person
  • You have a pet
  • You have a pet owner which is nothing than a person associated with a pet (or several pets). This is similar to a bridge table in a database.

So, you could maintain the persons and pets independently. If you want to create a pet owner then you would only care about associating a person with a pet.

Taking an event driven system you would talk about a pet aggregate, a person aggregate, and a root aggregate for pet owner. If you send a command to pet owner root aggregate (for creating or updating, e.g. adding/removing a pet or changing the pet owner), then you would simply store the pet/owner ids in root aggregate, nothing more. At this point you may not want to make sure if the references to owner/pet(s) exist at all or if they are correct. But when you begin to operate on the pet owner (e.g. on the read side of the CQRS), then you could check if everything is place as expected and start any problem handling actions by issuing futher commands, if needed.
To be able to do such a check, you could project the owner and pet events, as you have mentioned yourself. But I would suggest to handle this issue outside of the root aggregate, you could do that e.g. in a Pet Owner Manager which also would send commands to the three aggregates above.

Following the idea above, you would not need a transaction (saga) at all.

In general, be sparingly with transactions/sagas as much as possible in an event driven system. Most of time you won’t need it at all.

Thanks for your answer.

Yes, every event is stored and if something goes wrong with event derlivery (e.g. because the recipient throws and exception) then the Axon Server tries to redeliver it several times before it gives it up.

I guess this is the crux of my question. How can I make an atomic transaction which starts with the event in Axon Server and ends with the command-side projection being updated (through the subscribing event processor). I just wanted to understand the interactions between the different component - maybe even a commentary on the “unit of work” concept and how it works.

This attempts are repeated every time you start your service which registers the event handler. Be aware that your handler logic shoud not make use of exceptions for catching unexpected event content.

Is that true for subscribing processors as well? How would the subscribing processor know where to start in the event stream if the app is restarted?

If you conceptually relax the coupling then you will see that things get easier to implement and maintain. In general, I feel you logically couple the two issues (owner and pet) too closely in your concept.

Sometimes we have a model with a hard constraint which cannot be relaxed. Please ignore the silliness of the example. It is just there to facilitate discussion on subscribing event processors/transactions. I agree - you are right - the model does not make sense.

This works only for subscribing handlers, not for streaming one. A token tracking mechanism knows what events were already delivered successfully. You have to configure this tracker, I think the default one is an in-memory configuration, which loses the track on every start.

My suggestions

  1. Take a deeper look on CQRS
  2. Dive deeper in Event Driven Systems
  3. Go through the entire AxonIQ docs, I like it.
  4. Study the Axon samples on Github

This book may be of interest: Microservices Patterns, Manning