EngineeringSenior DeveloperArchitecture and Patterns

02 - Vertical Slice Architecture Blueprint

Thay vì chia theo Layer (Controller → Service → Repository), chia theo Feature. Mỗi feature là 1 slice cắt xuyên từ UI đến Database.


Tổng Quan

Khác biệt cốt lõi: Không có shared Service layer. Mỗi feature TỰ CHỨA logic, tự truy cập DB. Thay đổi feature A KHÔNG ảnh hưởng feature B.


Khi Nào Dùng / Không Dùng

✅ Dùng khi❌ Không dùng khi
Features ĐỘC LẬP nhau (ít share logic)Business rules CHIA SẺ nhiều giữa features
Team muốn ship feature nhanh, ít ceremonyCần rich Domain Model (DDD)
CQRS pattern (read/write tách biệt)Team quen Clean Architecture, không muốn đổi
Microservices (mỗi service nhỏ, ít layers)Cần enforce strict domain invariants

Dự án hay dùng: API microservices, BFF (Backend for Frontend), Admin tools, MVP nhanh.


Project Structure

MyApp/
├── MyApp.sln
├── src/
│   └── MyApp.API/
│       ├── Features/
│       │   ├── Orders/
│       │   │   ├── CreateOrder.cs        ← Command + Handler + Endpoint
│       │   │   ├── GetOrderById.cs       ← Query + Handler + Endpoint
│       │   │   ├── CancelOrder.cs
│       │   │   └── OrderDto.cs
│       │   ├── Products/
│       │   │   ├── CreateProduct.cs
│       │   │   ├── ListProducts.cs
│       │   │   └── ProductDto.cs
│       │   └── Users/
│       │       ├── Register.cs
│       │       └── Login.cs
│       ├── Common/
│       │   ├── AppDbContext.cs
│       │   ├── Behaviors/
│       │   │   └── ValidationBehavior.cs
│       │   └── Extensions/
│       │       └── ResultExtensions.cs
│       ├── Program.cs
│       └── appsettings.json
└── tests/
    └── MyApp.API.Tests/
        └── Features/
            └── Orders/
                ├── CreateOrderTests.cs
                └── GetOrderByIdTests.cs

Code Blueprint - 1 Vertical Slice

csharp
// File: Features/Orders/CreateOrder.cs - TẤT CẢ trong 1 file!

// === Request (Command) ===
public record CreateOrderCommand(Guid ProductId, int Quantity) : IRequest<Result<Guid>>;

// === Validator ===
public class CreateOrderValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.ProductId).NotEmpty();
        RuleFor(x => x.Quantity).GreaterThan(0);
    }
}

// === Handler ===
public class CreateOrderHandler(AppDbContext db) : IRequestHandler<CreateOrderCommand, Result<Guid>>
{
    public async Task<Result<Guid>> Handle(CreateOrderCommand cmd, CancellationToken ct)
    {
        var product = await db.Products.FindAsync([cmd.ProductId], ct);
        if (product is null)
            return Result.Failure<Guid>("Product not found");

        var order = new Order
        {
            Id = Guid.NewGuid(),
            ProductId = cmd.ProductId,
            Quantity = cmd.Quantity,
            Total = product.Price * cmd.Quantity
        };

        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);
        return Result.Success(order.Id);
    }
}

// === Endpoint ===
public static class CreateOrderEndpoint
{
    public static void Map(IEndpointRouteBuilder app) =>
        app.MapPost("/api/orders", async (CreateOrderCommand cmd, ISender sender) =>
        {
            var result = await sender.Send(cmd);
            return result.IsSuccess
                ? Results.Created($"/api/orders/{result.Value}", result.Value)
                : Results.BadRequest(result.Error);
        })
        .WithTags("Orders")
        .RequireAuthorization();
}

So sánh Clean Architecture vs Vertical Slice

Tiêu chíClean ArchitectureVertical Slice
Số projects4-51-2
Shared codeNhiều (interfaces, base classes)Ít (copy nếu cần)
Feature isolationTrung bìnhCAO
Domain complexityTốt cho DDDTốt cho CRUD
Learning curveCaoThấp
Merge conflictsCao (shared layers)Thấp (isolated features)