Vertical Slice Architecture vs Clean Architecture

Vertical Slice Architecture vs Clean Architecture

  • avatar
    Name
    Meysam Hadeli
  • Traditional Approach with Clean Architecture

    In the traditional approach using Clean Architecture, concerns are separated into some layers and dependencies are organized in an "Inward Pointing" manner. Each layer has it's own concern and responsibility.

    Layers

    • Api: This layer is responsible for handle user interaction through some controllers and endpoints. this layer has reference to application, infrastructure and core layer according rules.
    • Application: This layer responsible for implementing the use cases ,application services and putting some application contracts like repository contracts, messaging contracts and external contracts (such as http-client contracts and etc). this layer has reference to core layer according rules.
    • Infrastructure: This layer responsible for implementing data persistance like Orm providers (such as EF-Core DbContext) and implementing repository contracts, messaging contracts and external contracts (such as http-client contracts and etc). this layer has reference to core layer and application layer according rules.
    • Core: This layer responsible for handle business logic and rules for ensuring always valid domain model. this layer has no dependency to other layers.

    Pros

    • Framework Independent: Clean architecture can be used with various frameworks and technologies for each layer.
    • Database Independent: Data-access concerns move to independent layer and we can change our database easily.
    • Api Independent: The Api project only care Api and we can change our technology base on needs.
    • Testable: Because the layers are dependent to each other through some abstractions like interface, the testing is easier with mocking this interfaces instead of some concrete class.

    Cons

    • Coupling: For Implement a new feature or fix a bug, we need add or fix related code in all layers.
    • Complexity: Clean architecture involves many layers and abstractions, which can make the application more complex and it may be more difficult to understand and modify the codebase.
    • Scalability: If the size of code increase in clean architecture our application will be fragile and any changes could have some side effect in other layers.

    Vertical Slice Architecture

    In Clean Architecture we split technical concern to some layer and because of that we had some issue with this architecture. each change in one of layer has many side effects in other layer.

    In Vertical Slice Architecture instead of splitting in technical aspect, we split our system to some business functionalities and inner each business we have some features/slices. for example in the catalog bounded context, we have product aggregate and for this aggregate we can create some feature/slice like CreateProduct, UpdateProduct. Each feature/slice is completely independent and isolated from other feature/slice.

    in this feature/slice we treat each request as a distinct use case. and each of request breaks down into "command" requests and "query" requests. Using this architecture leads us to CQRS (Command and Query Responsibility Segregation) pattern for split command and query.

    Instead of coupling across a layer, we couple vertically along a slice. Minimize coupling between slices, and maximize coupling in a slice.

    In the following we can change infrastructure (database, message brokers, etc) on feature/slice without any side effect in any part of system. it's completely isolated from other feature/slice.

    Structure of Vertical Slice Architecture

    Let'ts check the folder structure in this architecture. Here we have Product aggregate and have GetProductById feature/slice inside of catalog bonded context. we can add more feature/slice to this business functionality base on needs.

    Implementation of Vertical Slice Architecture

    In vertical slice flow, we treat each request as a slice. For example for CreateProduct feature/slice, Our flow will start with a Endpoint with name CreateProductEndpoint and inner this endpoint we handle the http request from out side of world and pass our request data with MediatR to our handler.

    This approach provides us with high cohesion, as it keeps all related code together and makes it easy to maintain in the future :)

    namespace Catalog.Products.Features.CreatingProduct;
    
    public record CreateProductRequestDto(string Name, string Description, decimal Price);
    
    public record CreateProductResponseDto(Guid Id);
    
    public record CreateProduct(string Name, string Description, decimal Price) : IRequest<CreateProductResult>
    {
        public Guid Id { get; init; } = Guid.NewGuid();
    }
    
    public record CreateProductResult(Guid Id);
    
    // Post api/catalog/products
    public static class CreateProductEndpoint
    {
        public static void MapCreateProductByIdEndpoint(this IEndpointRouteBuilder endpoint)
        {
            endpoint
                .MapPost("api/catalog/products", async (CreateProductRequestDto request,
                    IMediator mediator,
                    IMapper mapper,
                    CancellationToken cancellationToken) =>
                {
                    var command = mapper.Map<CreateProduct>(request);
    
                    var result = await mediator.Send(command, cancellationToken);
    
                    var response = new CreateProductResponseDto(result.Id);
    
                    return Results.Ok(response);
                })
                .Produces<CreateProductResponseDto>()
                .WithName("CreateProduct");
        }
    }
    
    internal class Handler : IRequestHandler<CreateProduct, CreateProductResult>
    {
        private readonly CatalogDbContext _catalogDbContext;
        private readonly IMapper _mapper;
    
        public Handler(CatalogDbContext catalogDbContext, IMapper mapper)
        {
            _catalogDbContext = catalogDbContext;
            _mapper = mapper;
        }
    
        public async Task<CreateProductResult> Handle(CreateProduct command, CancellationToken cancellationToken)
        {
            var product = _mapper.Map<Product>(command);
    
            var entityEntry = (await _catalogDbContext.Products.AddAsync(product, cancellationToken)).Entity;
            await _catalogDbContext.SaveChangesAsync(cancellationToken);
    
            return new CreateProductResult(entityEntry.Id);
        }
    }
    

    You can find the sample code in this repository:

    🔗 https://github.com/meysamhadeli/blog-samples/tree/main/src/vertical-slice-architecture-sample

    Conclusion

    In this article we focus in Vertical Slice Architecture for handel Coupling in traditional architecture like Clean Architecture with split our business functionality to some feature/slice. this architecture leads us to use CQRS(Command and Query Responsibility Segregation) for split command and query and help us to treat each feature as a distinct use case. also this architecture provide to us Maintainability, that we can easily add new feature/slice or fix a bug in specific feature/slice.

    Reference

    https://jimmybogard.com/vertical-slice-architecture/