Service Calls From Saga/Aggregate CommandHandler

Hi,

This doesn’t exactly pertain to Axon specifically, but is more of a general CQRS question/scenario.

Playing around with CQRS and Axon, I’m attempting to make a sort of ecommerce app. Each Customer has a shopping cart which contains items, accepts commands like AddItem, Clear, Checkout and publishes events like ItemAdded, CartCleared, CartCheckedOut, etc.
The CartCheckedOut event begins a saga that handles order creation and payments (separate services/aggregates).

This is where I’m having issues:

Issue 1: My payments are being processed using Stripe, and each CustomerAggregate contains the Stripe customer id, so I need some way to get the Customer’s StripeId. Everywhere I’ve looked says that a Saga should not be contacting the query model, which leaves me to using a strange chain of commands and events as a way to request the StripeId from the Customer. This, I think, would involves calling CustomerAggregate.execute in order to publish an event containing the ID, which I’ve read is also bad practice.

I’m kind of stuck on what to do here. How do you request additional data that is not stored in an event? Because even if I made the StripeId a part of the event, I’d still have to request it somehow.

Issue 2: I’d like a Customer to have only 1 active Order at a time (ie. only one Order not ‘completed’). On top of this I’d like to send a response to the client if they have an order already and can’t checkout, which mean’s I’d need to do an Order projection lookup while processing the CheckoutCart command. Is this good practice? Or are there better ways to do such a check while ensuring that an error response can be returned to the client in the same request?

If I’m unclear in anything, I can do my best to elaborate.

Thanks,

Chris

Hi Chris,

Before answering your requests, I would like to say that the answers are my view on the topic, doesn’t mean that it has to be done like that necessarily.

Issue1: It’s not weird to have a saga sending a query to get additional information which is not present in the event(s). So, I’ll go with a query dispatching for this one.

Issue2: This check belongs to the Customer aggregate, I would add an AssignOrderToCustomer command which would set currentOrderId in the Customer aggregate. This command would be dispatched from the saga. Regarding the handling of this command, you can go two ways: throw an exception if there is currentOrderId assigned, or apply events OrderAssignedToCustomer and CustomerHasAssignedOrderAlready. This depends on whether having these events is important to your system.

Hope this helps!

Cheers,
Milan.

Hi Milan,

Thanks a tonne for your input! Definitely a relief to hear from someone that queries in a saga are fine. Everywhere that I read was pretty against doing this.
I like your idea for Issue2, however in terms of alerting the client that their cart was not checked out due to an active order, I don’t see how this is possible without a push notification of some sort. Ideally I’d like some exception or something to happen within the scope of the single checkout command. This way the command request can return some HTTP 4XX response to let the client know right away that their cart wasn’t checked out.

Any thoughts?

Thanks,
Chris

Hi Chris,

I recommend considering this as a ‘typical’ set validation problem. There are basically 3 ways of dealing with them:

Reservation pattern: create an order, try to get it confirmed in other places. This is essentially what Milan proposed, and has the downside of not being able to provide immediate feedback in the first command.

Build unique index: create a ‘table’ in which an order will attempt to insert the customer id (which has a unique index) for which it is creating it. If a value is already in there, it fails. When the order is closed/cancelled, the entry can be removed. Downside is that there is a contention point in the ‘table’.

Third: let it happen. Use view models to query for obvious collisions. In the (assumingly) rare case of a collision, we’ll fix it when we get there. It’s definitely the cheapest and simplest option, but nit recommended in life-or-death consistency requirement situations.

Hooe this helps.
Cheers,

Allard

Thanks for the tips, Allard.
I was thinking about this issue and an idea i have builds off of your 2nd idea of a table.
What if ShoppingCarts have state in that they are ‘ready-to-checkout’ and ‘not-ready-to-checkout’. When an order gets created, apply an event to the cart that changes it’s state and only allow checkouts if the cart says it’s ready.
To deal with eventual consistency (what if the user somehow checks out twice at (almost) the same time), I was reading that a reasonable solution is to check when the event is processed by the query side. Because the query eventHandler is the last step in the transaction of handling a command, it would be where inconsistencies could be best tested, and errors still thrown that can be caught by the controller.
Does Axon keep the query eventHandler in the transaction if it’s not part of the same JVM as the commandHandler?

Any critique on this idea would be appreciated.

Thanks,
Chris

I decided to add a ‘readyToCheckout’ boolean to the CartAggregate. When a CheckoutCommand is issued, it checks this boolean for true. If it’s true it then applies a checkedOutEvent which is picked up by a saga, and an eventSourcingHandler which sets the boolean to false. The saga then issues the createOrderCommand(handled by another saga eventually), waits for it to be completed, and issues a new command to re-allow checkouts.

Thanks again for your help.

Cheers,
Chris