Reservation

Problem

Consider a bike rental domain, where a renter can acquire a bike for a ride. The business flow involves selection and manual confirmation of the ride before it commences. From the moment of selection, the bike should be made temporarily unavailable for other renters until it has been confirmed for the current ride and for the duration of it, or become available anew should the acquisition be rejected by the renter in question.

Solution

Reserve a resource at the beginning of a business flow and claim it fully upon completion, or release the resource if the flow is interrupted.

Example

In the bike rental domain, the Rental bounded context is responsible for the bike acquisition flow (figure 1).

p4 1

Figure 1 - Business flow for bike acquisition by a renter

Figure 2 is the failure path where the renter changes their mind and cancels the ride, thus releasing the bike for other renters to select.

p4 2

Figure 1 - Failure path for bike acquisition

While each ride may be represented by an aggregate, reservations are managed by a single aggregate (listing 1).

@Aggregate
public class Rental {

    @AggregateMember
    private Map<String, RentalState> rentals = new HashMap<>();

    // ...

    @CommandHandler
    public void handle(ReserveBikeCommand command) {
        if (!rentals.get(command.bikeId()).equals(RentalState.AVAILABLE)) {
            throw new IllegalStateException("Bike is not available");
        }

        apply(new BikeReservedEvent(command.bikeId()));
    }

    @CommandHandler
    public void handle(ReleaseBikeCommand comand) {
        apply(new BikeReleasedEvent(command.bikeId()));
    }

    @EventSourcingHandler
    public void on(BikeReservedEvent event) {
        rentals.put(event.bikeId(), RentalState.RESERVED);
    }

    @EventSourcingHandler
    public void on(BikeReleasedEvent event) {
        rentals.put(event.bikeId(), RentalState.AVAILABLE);
    }
}

Listing 1 - Reserving and releasing bikes for renters