'DDD - Concurrency with quantity

Hi everyone,

I'm a little bit lost with a problem thinking in ddd way.

Imagine you have an application to sell concert ticket. So you have an entity which is called Concert with the quantity number and a method to buy a ticket.

class Concert {
  constructor(
    public id: string,
    public name: string,
    public ticketQuantity: number,
  ) {}

  buyTicket() {
    this.ticketQuantity = this.ticketQuantity - 1;
  }
}

The command looks like this:

async execute(command: BookConcertCommand): Promise<void> {
  const concert = await this.concertRepository.findById(command.concertId);

  concert.buyTicket();

  await this.concertRepository.save(concert);
}

Imagine, your application has to carry a lot of users and 1000 users try to buy a ticket at the same when the ticketQuantity is 500.

  • How can you ensure the invariant of the quantity can't be lower than 0 ?
  • How can you deal with concurrency here because even if two users try to buy a ticket at the same time the data can be false ?
  • What are the patterns we can use to ensure consistency and concurrency ?

Optimistic or pessismistic concurrency can't be a solution because it will frustrate a lot of users and we try to put all our logic domain into our domain so we can't put any logic inside sql/db or use a transactional script approach.



Solution 1:[1]

How can you ensure the invariant of the quantity can't be lower than 0

You include logic in your domain model that only assigns a ticket if at least one unassigned ticket is available.

You include locking (either optimistic or pessimistic) to ensure "first writer wins" -- the loser(s) in a data race should abort or retry.

If your book of record was just data in memory, then you would ensure that all attempts to buy tickets for concert 12345 must first acquire the same lock. In effect, you serialize the requests so that the business logic is running one at a time only.

If your book of record was a relational database, then within the context of each transaction you might perform a "select for update" to get a copy of the current data, and perform the update in the same transaction. The database will raise it's flavor of concurrent modification exception to the connections that lose the race.

Alternatively, you use something like the semantics of a conditional-write / compare and swap: you get an unlocked copy of the concert from the book of record, make your changes, then send a "update this record if it still looks like the unlocked copy" message, if you get the response announcing you've won the race, congratulations - you're done. If not, you retry or fail.

Optimistic or pessismistic concurrency can't be a solution because it will frustrate a lot of users

  1. Of course it can
  2. If the concert is overbooked, they are going to be frustrated anyway
  3. The business logic doesn't have to run synchronously with the request - it might be acceptable to write down that they want a ticket, and then contact them asynchronously to let them know that a ticket has been assigned to them

It may be helpful to review some of Udi Dahan's writing on collaborative and competitive domains; for instance, this piece from 2011.

In a collaborative domain, an inherent property of the domain is that multiple actors operate in parallel on the same set of data. A reservation system for concerts would be a good example of a collaborative domain – everyone wants the “good seats” (although it might be better call that competitive rather than collaborative, it is effectively the same principle).

Solution 2:[2]

You might be following these steps:

1- ReserveRequested -> ReserveRequestAccepted -> TicketReserved

2- ReserveRequested -> ReserveRequestRejected

When somebody clicks on the buy ticket button, you should create a reserve request entity, and then you can process the reservation in the background and by a queue system.

On the user side, you can return a unique reserve request-id to check the result of the process. So the frontend developer should fetch the result of process periodically until it succeeds or fails.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 VoiceOfUnreason
Solution 2 Morilog