Blog Logo
TAGS

Vertical Slices in Practice - Event-Driven.io

I’m a preacher for the CQRS, Vertical Slices, and Feature Folders. I won’t hide that, and I won’t even try. I believe that structuring code based on the business feature helps deliver business value, thanks to an increased focus on the domain and reduced cognitive load. Today, I’ll take a step further and give more practical guidance on simple but closer to the real-world case. The focus will be on the structural part, but I’ll add a sprinkle of Event Sourcing and use Marten to make it more real. Let’s say we’d like to implement a Room Reservation module in the Hotel Management system. Reservation can be initiated either from the user through our UI and API call or from an external system like Booking.com. Our flow, for now, looks almost the same, and we don’t want to overcomplicate things. We could define the following events representing a business flow of the Reservation: public record RoomReserved ( string ReservationId, string? ExternalReservationId, RoomType RoomType, DateOnly From, DateOnly To, string GuestId, int NumberOfPeople, ReservationSource Source, DateTimeOffset MadeAt ); public record RoomReservationConfirmed ( string Id, DateTimeOffset ConfirmedAt ); public record RoomReservationCancelled ( string Id, DateTimeOffset CancelledAt ); public enum RoomType { Single = 1, Twin = 2, King = 3 } public enum ReservationSource { Api, External } public enum ReservationStatus { Pending, Confirmed, Cancelled } As you see, when reserving a room in the hotel, you’re not booking the specific room but the room type. That has intriguing consequences that we’ll discuss later on. To make our decisions, we need a Room Reservation entity representing the current state of our reservation process. It could look like that. public record RoomReservation ( string Id, RoomType RoomType, DateOnly From, DateOnly To, string GuestId, int NumberOfPeople, ReservationSource Source, ReservationStatus Status, DateTimeOffset MadeAt, DateTimeOffset? ConfirmedAt, DateTimeOffset? CancelledAt ) { public static RoomReservation Create(RoomReserved reserved) => new( reserved.ReservationId, reserved.RoomType, reserved.From, reserved.To, reserved.GuestId, reserved.NumberOfPeople, reserved.Source, reserved.Source == ReservationSource.External ? ReservationStatus.Confirmed : ReservationStatus.Pending, reserved.MadeAt, reserved.Source == ReservationSource.External ? reserved.MadeAt : null, null ); public RoomReservation Apply(RoomReservationConfirmed confirmed) => this with { Status = ReservationStatus.Confirmed, ConfirmedAt = confirmed.ConfirmedAt }; public RoomReservation Apply(RoomReservationCancelled confirmed) => this with { Status = ReservationStatus.Cancelled, ConfirmedAt = confirmed.CancelledAt }; } In the RoomReserved event apply method, we already see