An example of an incorrect hexagonal architecture
  • TypeScript 52.7%
  • Python 47.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-06-20 15:01:45 +02:00
python Move vscode Python settings to the correct location 2025-10-27 22:36:03 +01:00
ts Better starting point : vitest coverage 2026-06-20 14:44:51 +02:00
.gitignore Igore sqlite journal 2026-04-01 19:20:52 +02:00
README.md Better README 2026-06-20 15:01:45 +02:00
skyspell-ignore.toml Make room for other programming languages 2025-10-27 19:14:34 +01:00

Train Reservation with a pentagonal architecture

Goal

Learn about the hexagonal architecture, by using tests to drive the refactorings.

Also, learn the difference betweed a CRUD app (which only does create, read, update, delete) and an app that has business logic.

Context

You are implementing a back end API to book seats from trains.

Each train has an ID.

Each train contains coaches, identified with letters (A, B, C ...)

Each coach contain seats, which have a number containing the coach letter : "1A", "2A", and so on.

Each seat also has an optional booking reference (depending if they are free or already booked).

For now, two routes are implemented:

GET /trains

and

GET /trains/<name>/seats

Instructions

First, choose a programming language, and check you can :

  • run the tests
  • call the /trains and /trains/express_2000/seats routes (using curl for instance)

Part 1

Answer the following questions:

  • Are you confident that the code is working as expected ?
  • Do you think there are missing tests ?
  • Do you think the architecture is the right one ?

Part 2

Create a branch, and implement a new feature: the GET /trains/{name}/seats route should also return the booking reference of each seats, like this:

GET /trains/express_2000/seats

{
    "seats": [
      { "number": "1A", "booking_reference": "abc123" },
      { "number": "2A", "booking_reference": "abc123" },
      { "number": "3A", "booking_reference": "" },
      { "number": "4A", "booking_reference": "" },
    ]
}

Make sure the tests still pass.

What do you think about the way the test code changed ?

Part 3

Go back to the main branch, and rewrite the tests to not use any mocks.

Part 4

Re-implement the same feature as in part 2

Compare with the tests you had at the end of part 2.

Part 5

Implement the POST /book route:

The body should look like:

{
  "train_id": "express_2000",
  "seats": ["1A", "2A"],
  "booking_reference": "abc123def"
}

Note that the server must prevent you from booking a seat that is already reserved with another booking reference, by returning a "409 conflict" status code.

It is however OK to try and book the same seat with twice with the same booking reference.

Implement this new route. Is the app still a CRUD app ?

Part 6

First off, your users won't know their seat numbers in advance !

So the POST /book should instead take a seat count:

{
  "train_id": "express_2000",
  "seat_count": 2,
  "booking_reference": "abc123def"
}

(you can assume the booking reference is fetched form an other source at this point)

Your code should then find some free seats and return them::

{
  "booked": ["1A", "2A"]
}

or return an error message if no free seats are available.

Part 7

You need to implement more business rules:

  • It should not be possible to book seats that are not in the same coach.

For instance, trying to book seat "1A" and "1B" should fail.

  • The app should try and maintain 70% occupancy for each coach

For instance, let's say each coach has 100 seats, and 80% of the seats in coach A are booked, but all the seats in coach B are free. Then, after trying to book 2 seats you should return 2 seats from coach B.

This time, it should be clear you app is no longer just a CRUD - time to do some serious refactoring :)