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.