4

I have a web application where a user can customize one or more sandwiches to order.
Once the user has set up his sandwich, the sandwich is added to an unsubmitted order.
The user can add to the order has many different sandwiches has he or she likes.
When the user is satisfied he can submit the order for further processing.

I have decided to model three aggregates: User, Sandwich, and Order.

An Order has a collection of OrderItem, each OrderItem references a Sandwich of the User.
A Sandwich as a reference to its Order.

When the User completes a Sandwich and submits it, a new Order for that Sandwich should be created if it does not already exist and the new Sandwich should be assigned to the Order.

You might think of the unsubmitted Order as the shopping cart of an e-commerce application.

I'm trying to obey the rule: one aggregate per transaction, but I'm having difficulties finding an implementation that models this situation clearly.
Theoretically, if I want to keep the Order and the Sandwiches consistent, I must first create an Order and the Sandwiches and then save them together. This involves creating and storing two aggregates in one transaction, which breaks the rule.

To avoid creating two aggregates at once in one transaction, I decided to create a Sandwich first, which will generate a SandwichCreated domain event. Handling SandwichCreated will create an Order, if it does not exists, and then the Order will add the Sandwich as an OrderItem, generating a SandwichAddedToOrder. SandwichAddedToOrder will be used to update the order reference on the Sandwich. This approach requires the concept of a "currently unsubmitted" Order for the User, because I otherwise would not be able to know which Sandwich belongs to which Order.

What I find "wrong" about this approach: there is a heavy "back and forth" between the Sandwich and the Order aggregates that obfuscates the actual business logic workflow. Another issue would be if the sequence of the events breaks it might leave the system in an inconsistent state, but I guess that is a side effect of allowing eventual consistency.

Is there a better approach to handle this kind of situation while still obeying the "one transaction per aggregate rule"? Additionally, how can I model this workflow in, preferably, one place so that it is not lost in many different event handlers making it hard to read and reason about?

Edit with a clarification:

I have decided to model a Sandwich as an aggregate, because each Sandwich of every User is retrieved without considering the Order. When the cooks prepare the sandwiches, they do not care about which Order the Sandwich belongs to, they want a list of Sandwich not Order.
A cook can choose a Sandwich to prepare and can change its status to something like: "Preparing", "Ready to pick up", and "Ingredients not available". Once all the Sandwiches of an Order are either "Ready to pick up" or "Cannot prepare" the Order is considered "Complete" and payment will be requested.
To be able to "choose a Sandwich to prepare" and individually change its status, a Sandwich must have an identity, making it an aggregate.
Another use case is that the cook is able to write a "reason" why a Sandwich might not be available, further cementing (at least to me) the fact that a Sandwich can be considered an aggregate.

It is true that I could use OrderItem to hold all Sandwich status, but I don't like this idea because the business might add beverages and delivery. Then I would have OrderItem for delivery and beverages with some bogus status that exists solely because a Sandwich has status, thus forcing other entities to have attributes that are not needed.

10
  • 3
    Tell me why we need a sandwich aggregate rather than simply an order aggregate. Commented Dec 5, 2020 at 22:09
  • @candied_orange I have clarified in the question why
    – Gwinn
    Commented Dec 6, 2020 at 16:14
  • Thanks for the clarification. I must agree with @QuestionDriven. I still don't see why a non-aggregate Sandwich can't have a status that an aggregate Order doesn't have. Or why you wouldn't want that status for beverages. I fail to see why a sandwich needs its own identity apart from the Order. Am I missing something? Commented Dec 6, 2020 at 17:48
  • @candied_orange Beverages do not have status because once bought they don't need any additional processing (a beverage is always available it can't be "In preparation", ignoring stock). I've given identity to the sandwich so that I can change the status of a sandwich independently of the others (e.g.: sandwich 1 is ready and sandwich 2 is not available). Note that the cooks have a list of all the sandwiches from many different orders that's why I thought to give them an identity.
    – Gwinn
    Commented Dec 6, 2020 at 19:27
  • 1
    @Gwinn: Just because a beverage won't use some of its statuses as much as a sandwich would, doesn't mean that beverages and sandwiches can't both rely on the same status set.
    – Flater
    Commented Dec 7, 2020 at 11:00

1 Answer 1

6

There are two different approaches to untangle the situation.

  1. Treat Sandwich as a part of Order aggregate, not as a separate aggregate
  2. Remove the reference from Sandwich to Order and follow the one aggregate per transaction rule

If every Sandwich instance must belong to an Order (or a Cart) under your business rules, then I think the 1st option is the way to go.

If you want to save the customized Sandwiches and reuse them in the future, like for easy re-ordering, then the 2nd option would be better.

To determine if it's an aggregate or not, you have to answer the question: "Can a Sandwich exist without an Order or not ?"

An Aggregate is a logical boundary of objects which can be built independently. If you can't follow the one aggregate per one transaction rule, I think it's better to rethink the model instead of bypass the rule.

3
  • I have clarified in the question why I think that a Sandwich is an aggregate
    – Gwinn
    Commented Dec 6, 2020 at 16:15
  • I have rethought my design and decided that, in fact, a Sandwich cannot exists outside an Order
    – Gwinn
    Commented Dec 7, 2020 at 8:09
  • Bravo! I'd point out that what the customer most likely cares about is the order being prepared as opposed to the individual sandwich preparation. As such, the status should belong to the Order aggregate anyway. Commented Dec 16, 2020 at 6:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.