Vertical Slices in Practice

theanonymousone1 pts0 comments

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.

I explained already in my articles why Generic does not mean Simple and how to slice the codebase effectively. I showed that in my talk:

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<br>string ReservationId,<br>string? ExternalReservationId,<br>RoomType RoomType,<br>DateOnly From,<br>DateOnly To,<br>string GuestId,<br>int NumberOfPeople,<br>ReservationSource Source,<br>DateTimeOffset MadeAt<br>);

public record RoomReservationConfirmed<br>string ReservationId,<br>DateTimeOffset ConfirmedAt<br>);

public record RoomReservationCancelled<br>string ReservationId,<br>DateTimeOffset CancelledAt<br>);

public enum RoomType<br>Single = 1,<br>Twin = 2,<br>King = 3

public enum ReservationSource<br>Api,<br>External

public enum ReservationStatus<br>Pending,<br>Confirmed,<br>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<br>string Id,<br>RoomType RoomType,<br>DateOnly From,<br>DateOnly To,<br>string GuestId,<br>int NumberOfPeople,<br>ReservationSource Source,<br>ReservationStatus Status,<br>DateTimeOffset MadeAt,<br>DateTimeOffset? ConfirmedAt,<br>DateTimeOffset? CancelledAt<br>public static RoomReservation Create(RoomReserved reserved) =><br>new(<br>reserved.ReservationId,<br>reserved.RoomType,<br>reserved.From,<br>reserved.To,<br>reserved.GuestId,<br>reserved.NumberOfPeople,<br>reserved.Source,<br>reserved.Source == ReservationSource.External ? ReservationStatus.Confirmed : ReservationStatus.Pending,<br>reserved.MadeAt,<br>reserved.Source == ReservationSource.External ? reserved.MadeAt : null,<br>null<br>);

public RoomReservation Apply(RoomReservationConfirmed confirmed) =><br>this with<br>Status = ReservationStatus.Confirmed,<br>ConfirmedAt = confirmed.ConfirmedAt<br>};

public RoomReservation Apply(RoomReservationCancelled confirmed) =><br>this with<br>Status = ReservationStatus.Cancelled,<br>ConfirmedAt = confirmed.CancelledAt<br>};

In the RoomReserved event apply method, we already see that external reservation has a different flow than the API one. We assume that it’s already confirmed and paid once we get it. That’s different from our regular reservation. It’ll need to go through an additional flow and be explicitly confirmed after making payment etc.

We could define union types and different events for internal and external reservations, but let’s focus today on the structure rather than the modelling.

As we have more stuff around the room reservation process, let’s create a dedicated folder RoomReservations. Inside it, we can define the RoomReservation.cs file and put the code we described above. It shapes our domain model. We’ll be working around it when defining our process. When we add a new event, we must update the entity, etc. It also forms documentation of our flow.

Let’s now implement the business logic for our room reservation . Let’s create a nested folder called ReservingRoom. It’ll encapsulate the reservation initiation process. We’ll also need a command and its handler. Let’s define the ReserveRoom.cs file inside the newly created folder.

We’ll keep there both command definition and business logic for the operation. In most cases, when we’re changing command, we need to change logic. And when we change logic, we’d like to see the command definition. It’s about ergonomy and developer experience.

It could look like that:

public record ReserveRoom<br>string ReservationId,<br>RoomType RoomType,<br>DateOnly From,<br>DateOnly To,<br>string GuestId,<br>int NumberOfPeople,<br>DateTimeOffset Now,<br>ReservationSource ReservationSource,<br>IReadOnlyListDailyRoomTypeAvailability> DailyAvailability,<br>string? ExternalId<br>public static RoomReserved Handle(ReserveRoom command)<br>var reservationSource = command.ReservationSource;

var dailyAvailability = command.DailyAvailability;

if (reservationSource == ReservationSource.Api && dailyAvailability.Any(a => a.AvailableRooms 1))<br>throw new InvalidOperationException("Not enough available rooms!");

return new...

public reserved reservationsource reservation string confirmed

Related Articles