6

Consider this simplified example: In an online ticket sale website, tickets have variable prices that change over time.

User searches for tickets. Once he finds a ticket he wants, clicks "Buy" and then a quote is issued. Quotes are persisted and they have they own locator that is sent by email. The quote page opens, showing the breakdown of what he is going to pay, and a button to pay. At this point, the ticket is still available for others. If the user clicks on "Pay now", a payment process starts and a Purchase Order is issued with his details.

When a purchase order is paid, the quote status must be set as "Accepted" and reference the purchase order; the ticket status is set as "Sold".

As per requirements, this must happen in an atomic fashion, meaning that it is not acceptable to have a paid purchase order referencing a non accepted quote, or a non sold ticket at any given time. Essentially, eventual consistency is not allowed.

There are two big aggregates, Quote and PurchaseOrder. The first manages the breakdown calculation, taxes, etc... The second manages payment details for a quote, payment event raising, etc... The problem I have is that right now it seems I have to use a transaction across ticket, quote and purchase order when the last is paid.

How may I model this honoring the "Transactions should not cross aggregate boundaries" rule?

  • I'm going to be shallow and drop some links for you: lostechies.com/gabrielschenker/2015/05/25/ddd-the-aggregate and vaughnvernon.co/?p=838 – Robzor Jun 27 '17 at 22:07
  • If they need to be in the same transaction boundary, then Quote and Purchase Order are not likely to be aggregates. They are artifacts (perhaps entities) managed by the aggregate. They actually sound like documents (without behavior) which are produced in the process of executing aggregate behaviors. – Kasey Speakman Jun 27 '17 at 22:27
  • Would not that lead to the "too big aggregate" issue? I thought about a ReservationAggregate, however a quote is not a reservation. – NullOrEmpty Jun 28 '17 at 7:31
6

You need a process manager/saga that do exactly what you need without using transaction that spawn multiple aggregates.

Basically that Saga would reserve the ticket right before the payment begins and would markTicketAsSold after the payment is successful or releaseTheReservation after the payment is failed.

This process and the invariant that a reserved or sold ticket cannot be reserved again would ensure that the system as a hole is in the correct state at all times.

As per requirements, this must happen in an atomic fashion, meaning that it is not acceptable to have a paid purchase order referencing a non accepted quote, or a non sold ticket at any given time. Essentially, eventual consistency is not allowed.

This does not seem to be a valid invariant. The invariant should be "a sold ticket cannot be sold again". Carefully defining the business invariants will help you in designing the aggregate boundaries and correctly modeling the long running processes.

  • But when you commit the saga, still have to modify ticket, quote and purchase order state at the same time. Same problem again. – NullOrEmpty Jun 28 '17 at 7:20
  • @NullOrEmpty I don't understand what you mean by commiting the Saga – Constantin Galbenu Jun 28 '17 at 7:21
  • @NullOrEmpty I think my commit term from the answer is not ok. I will edit it – Constantin Galbenu Jun 28 '17 at 7:24
  • @NullOrEmpty You must carefully define the invariants. In your case "it is not acceptable to have a paid purchase order referencing a non accepted quote" does not seem to be a valid invariant but "a sold ticket can not be sold again" is – Constantin Galbenu Jun 28 '17 at 7:28
  • 1
    Well it is a requirement that in the purchase order page, where purchase order, quote and ticket data are shown, states are consistent at any given time. – NullOrEmpty Jun 28 '17 at 7:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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