Grokking Spring Boot
Vote

0% completed

​

The Booking Service

The Domain

The Rules

The API

Two Customers, One Seat

How the Course Closes the Gap

Key Takeaways

Practice Questions

A customer opens an event ticketing app and picks an evening concert at Riverside Hall. The app shows a seat map, with free seats in one color and taken seats in another. The customer chooses seat A12, pays, and receives a confirmation by email.

Behind those steps sits the booking service, the backend that this course builds. So far it has one endpoint that says it is running. It needs a model of venues, events, and seats, rules that protect those seats, and an API that the app can call.

What follows describes all three, then runs the first problem the service must solve: two customers who want the same seat at the same moment.

The Domain

The domain is the part of the real world that the software models: here, the things that people in a ticketing business talk about and the links between them. The booking service needs five kinds of record:

  • Venue. A place that holds events, with a name and a seat layout, such as Riverside Hall with 20 rows of 30 seats.
  • Event. One performance at one venue, at one time. Each event has its own seats, because seat A12 tonight and seat A12 tomorrow are different products.
  • Seat. One place at one event, named by row and number, such as A12. A seat is free or taken.
  • Customer. A person who books seats, known by an email address.
  • Booking. The record that one customer holds one seat at one event. It is created with the status CONFIRMED when payment succeeds. When the customer cancels, the booking is kept as a record, its status becomes CANCELED, and it no longer holds the seat.
The five kinds of record in the booking service, and how a booking links a customer to a seat at one event.
The five kinds of record in the booking service, and how a booking links a customer to a seat at one event.

Notice that a booking is the only record that connects a customer to a seat. A seat record does not store who holds it, so the question "is A12 free?" means "does any confirmed booking hold A12 for this event?"

The Rules

A business rule is a condition that must always hold, whatever order the requests arrive in. The booking service has four:

  1. A seat is booked at most once per event. Two confirmed bookings may never hold the same seat at the same event.
  2. A booking exists only after payment succeeds. A failed payment leaves the seat free.
  3. A customer may cancel a booking before the event starts. The seat then becomes free again.
  4. Only venue staff create and change events. Customers can read events and book seats, nothing more.

Most of the hard work in the service exists to protect these four rules. The first rule, in particular, sounds trivial and is not, as the run below shows.

The API

The API is the set of HTTP requests that the app sends to the service, each one a method and a path. These are the endpoints the course builds, with who may call each one:

Method and pathWhat it doesWho may call it
GET /eventsLists upcoming eventsAnyone
GET /events/{id}/seatsShows free and taken seats for one eventAnyone
POST /bookingsBooks one seat for the signed-in customerCustomers
GET /bookings/{id}Shows one bookingIts customer
DELETE /bookings/{id}Cancels a booking before the event startsIts customer
POST /eventsCreates an event at a venueVenue staff

The text {id} in a path stands for a value, such as 7 in GET /events/7/seats. The most demanding endpoint is POST /bookings, because it must check a seat, charge the customer, and save the booking without breaking rule 1 or rule 2.

Two Customers, One Seat

The obvious way to book a seat takes two steps: check whether the seat is free, and if it is, save a booking. The program below runs exactly those steps against an in-memory store. Run 1 handles Ana's request and then Ben's. Run 2 lets the steps of the two requests interleave, as they can when two requests are served by two threads at the same time. The order of the steps is written into the program, so the output is the same on every run.

Java
Java

. . . .

It prints the following:

Run 1: the two requests take turns, one after the other ana@example.com: booking 1 confirmed ben@example.com: seat A12 is already taken Confirmations for seat A12: 1 Seat map says A12 belongs to: ana@example.com Run 2: the steps of the two requests interleave step 1: Ana's request checks A12, free = true step 2: Ben's request checks A12, free = true step 3: ana@example.com: booking 1 confirmed step 4: ben@example.com: booking 2 confirmed Confirmations for seat A12: 2 Seat map says A12 belongs to: ben@example.com

In run 1 the rule holds: Ana gets A12, and Ben is told it is taken. In run 2, both checks happen before either save, so both requests see a free seat. Both customers receive a confirmation, yet the seat map names only Ben. Ana has paid for a seat that belongs to someone else.

The four steps of two booking requests, interleaved so that both see seat A12 as free.
The four steps of two booking requests, interleaved so that both see seat A12 as free.

Notice that nothing in run 2 is a bug in a single line. Each step is correct alone. The failure comes from the gap between a check and the action that depends on it, which another request can use. This kind of failure, where the result depends on the timing of concurrent work, is called a race condition.

A web server makes run 2 normal, not rare. Tomcat serves requests on many threads at once, so on a busy night two requests for a popular seat will sometimes interleave exactly like this. Testing one request at a time never shows it.

How the Course Closes the Gap

Several tools can protect rule 1, and each has a cost. A database can refuse a second confirmed booking for the same seat at the same event. A version number on each seat can make the second save fail, which is the subject of Optimistic Locking. A lock can make the second request wait until the first one finishes. Choosing among them needs transactions, data access, and the way Spring wraps your methods, so those come first.

The service grows in this order:

  • Spring itself: how the container builds and manages the objects of the service.
  • The API: controllers, input validation, and clear errors for POST /bookings and the other endpoints.
  • Data and transactions: storing events and bookings in a database, and protecting rules 1 and 2.
  • Security: customers, staff, and who may call which endpoint, for rule 4.
  • Tests, performance, and production: proving the rules hold, handling heavy traffic on busy nights, and running the service safely.

Key Takeaways

  • The booking service models venues, events, seats, customers, and bookings, and a booking is the only link between a customer and a seat.
  • Four business rules protect the business, and the hardest is that a seat is booked at most once per event.
  • The API is a small set of endpoints, and POST /bookings must check, charge, and save without breaking a rule.
  • A check followed by a save is unsafe when two requests interleave: both can see a free seat and both can save.
  • A failure that depends on the timing of concurrent requests is a race condition, and testing one request at a time does not find it.

The service will use modern Java throughout: records like the ones in this program, lambdas, streams, and annotations. Lambdas and Functional Interfaces starts that review with the feature that Spring APIs use most.

Practice Questions

1. Why does each event have its own seats, instead of each venue having one set of seats?

<details><summary>Show answer</summary>

Because a seat is sold for one performance, not for a venue forever. Seat A12 for tonight's concert and seat A12 for tomorrow's play are different things a customer can buy. If seats belonged only to the venue, booking A12 tonight would block it for every later event.

</details>

2. In run 2, which single change to the order of the four steps would have kept rule 1 intact?

<details><summary>Show answer</summary>

Moving Ana's save, step 3, before Ben's check, step 2. Ben's request would then see A12 as taken. The rule fails only when both checks happen before either save, which is why the check and the save must act as one step that no other request can split.

</details>

3. A tester books seat A12 five hundred times in a row from one script and never sees a double booking. Does this show that POST /bookings is safe?

<details><summary>Show answer</summary>

No. The script sends one request at a time, so the steps of two requests never interleave. That is run 1, repeated. A race condition appears only when requests overlap, so a test must send concurrent requests for the same seat to have a chance of finding it.

</details>

4. The payment succeeds, but saving the booking fails because the database is briefly unavailable. What has gone wrong, and what does the service owe the customer?

<details><summary>Show answer</summary>

The customer has paid, but no booking exists, so the service has taken money for nothing. No rule is broken as written, but the result is still wrong. The service must either save the booking once the database returns, or refund the payment. Later lessons on transactions and failure handling show how to make the charge and the booking succeed or fail together, or to undo the charge when they cannot.

</details>

Reading Progress

0%


Vote for new content

On This Page

The Domain

The Rules

The API

Two Customers, One Seat

How the Course Closes the Gap

Key Takeaways

Practice Questions