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).
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.
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