Understanding Vertical Slice Architecture: Structuring Vertical Slices

What is a Vertical Slice?

A vertical slice represents a self-contained unit of functionality, slicing through the entire application stack. It encapsulates all the code and components necessary to fulfill a specific feature. Unlike traditional layered architectures, where code is organized horizontally across various layers and one feature's implementation can be scattered across these layers, a vertical slice addresses this by grouping all the code for a feature into a single, cohesive slice.

Benefits of Vertical Slice Architecture (VSA)

Adopting Vertical Slice Architecture offers several advantages.

  • Improved Cohesion: Code related to a specific feature resides together, making it easier to understand, modify, and test.
  • Reduced Complexity: VSA simplifies your application's mental model by avoiding the need to navigate multiple layers.
  • Focus on Business Logic: The structure naturally emphasizes the business use case over technical implementation details.
  • Easier Maintenance: Changes to a feature are localized within its slice, reducing the risk of unintended side effects.

VSA

Implementing Vertical Slice Architecture

Here's an example vertical slice representing the CreateProduct feature. We use a static class to represent the feature and group the related types. Each feature can have respective Request and Response classes, and the use case with the business logic can be in a Minimal API endpoint.

A vertical slice typically follows the Command Query Responsibility Segregation (CQRS) pattern, which separates read (query) and write (command) operations.

public static class CreateProduct
{
    public record Request(string Name, decimal Price);
    public record Response(int Id, string Name, decimal Price);

    public class Endpoint : IEndpoint
    {
        public void MapEndpoint(IEndpointRouteBuilder app)
        {
            app.MapPost("products", Handler).WithTags("Products");
        }

        public static IResult Handler(Request request, AppDbContext context)
        {
            var product = new Product
            {
                Name = request.Name,
                Price = request.Price
            };

            context.Products.Add(product);
            context.SaveChanges();

            return Results.Ok(
                new Response(product.Id, product.Name, product.Price));
        }
    }
}

This example demonstrates how the code for the entire CreateProduct feature is tightly grouped within a single file, making it extremely easy to locate, understand, and modify everything related to this functionality.

Handling Validation in Vertical Slices

Vertical slices often need to solve cross-cutting concerns, such as validation. Validation is crucial for preventing invalid or malicious data from entering your system. Using the FluentValidation library, we can easily implement validation within our slice.

public class Validator : AbstractValidator<Request>
{
    public Validator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
        RuleFor(x => x.Price).GreaterThanOrEqualTo(0);
        // ... other rules
    }
}

This validator can then be injected into your endpoint using dependency injection, allowing you to perform validation before processing the request.

public static class CreateProduct
{
    public record Request(string Name, decimal Price);
    public record Response(int Id, string Name, decimal Price);

    public class Validator : AbstractValidator<Request> { /* ... */ }

    public class Endpoint : IEndpoint
    {
        public void MapEndpoint(IEndpointRouteBuilder app)
        {
            app.MapPost("products", Handler).WithTags("Products");
        }

        public static async Task<IResult> Handler(
            Request request,
            IValidator<Request> validator,
            AppDbContext context)
        {
            var validationResult = await validator.ValidateAsync(request);
            if (!validationResult.IsValid)
            {
                return Results.BadRequest(validationResult.Errors);
            }

            var product = new Product
            {
                Name = request.Name,
                Price = request.Price
            };

            context.Products.Add(product);
            await context.SaveChangesAsync();

            return Results.Ok(
                new Response(product.Id, product.Name, product.Price));
        }
    }
}

Managing Complex Features and Shared Logic

For more complex features and shared logic, VSA can still be highly effective. Here are a few strategies.

  • Decomposition: Break down complex features into smaller, more manageable vertical slices, each representing a cohesive piece of the overall feature.
  • Refactoring: When a vertical slice becomes difficult to maintain, apply refactoring techniques, such as Extract Method and Extract Class.
  • Extract Shared Logic: Identify common logic used across multiple features. Create a separate class or extension method to reference it from your vertical slices as needed.
  • Push Logic Down: Write vertical slices using procedural code, like a Transaction Script. Then, identify parts of the business logic that naturally belong to the domain entities.

Understanding code smells and refactorings will help you and your team make the most of VSA.

Summary

Vertical Slice Architecture is more than just a way to structure your code. By focusing on features, VSA allows you to create cohesive and maintainable applications. Vertical slices are self-contained, making unit and integration testing more straightforward.

VSA brings benefits in terms of code organization and development speed, making it a valuable tool in your toolbox. Code is grouped by feature, making it easier to locate and understand. The structure aligns with the way business users think about features. Changes are localized, reducing the risk of regressions and enabling faster iterations.

Consider embracing Vertical Slice Architecture in your next project. It's a significant mindset shift from Clean Architecture, but both have their place and share similar ideas.


Similar Articles