Clean Architecture

লেসন ৩৬ / ৫০

Clean Architecture কী?

Robert C. Martin (Uncle Bob)-এর architecture — dependency inward, business logic framework-independent।

4 Layer Structure

MyApp.sln
├── src/
│   ├── MyApp.Domain/              # Entities, value objects (no deps)
│   ├── MyApp.Application/          # Use cases, interfaces (depends Domain)
│   ├── MyApp.Infrastructure/       # EF Core, external APIs (depends App)
│   └── MyApp.Api/                  # Controllers (depends App + Infra)
└── tests/
    └── MyApp.UnitTests/

Dependency Rule

Inner layer outer-কে জানে না। Domain → কারো উপর depend করে না। API → Infrastructure → Application → Domain।

Domain Layer

// MyApp.Domain/Entities/Product.cs
public class Product {
    public int Id { get; private set; }
    public string Name { get; private set; }
    public decimal Price { get; private set; }

    private Product() {}    // EF
    public Product(string name, decimal price) {
        if (price < 0) throw new DomainException("Price negative");
        Name = name; Price = price;
    }

    public void UpdatePrice(decimal newPrice) {
        if (newPrice < 0) throw new DomainException("Invalid");
        Price = newPrice;
    }
}

Application Layer

// MyApp.Application/Interfaces/IProductRepository.cs
public interface IProductRepository {
    Task<Product?> GetByIdAsync(int id);
    Task AddAsync(Product p);
    Task SaveChangesAsync();
}

// Use case (CQRS handler)
public class CreateProductHandler {
    private readonly IProductRepository _repo;
    public CreateProductHandler(IProductRepository r) => _repo = r;
    public async Task<int> Handle(CreateProductCommand c) {
        var p = new Product(c.Name, c.Price);
        await _repo.AddAsync(p);
        await _repo.SaveChangesAsync();
        return p.Id;
    }
}

Infrastructure Layer

// MyApp.Infrastructure/Persistence/ProductRepository.cs
public class ProductRepository : IProductRepository {
    private readonly AppDbContext _ctx;
    public ProductRepository(AppDbContext c) => _ctx = c;
    public Task<Product?> GetByIdAsync(int id) => _ctx.Products.FindAsync(id).AsTask();
    public async Task AddAsync(Product p) => await _ctx.Products.AddAsync(p);
    public Task SaveChangesAsync() => _ctx.SaveChangesAsync();
}

API Layer (Composition Root)

// Program.cs (MyApp.Api)
builder.Services.AddDbContext<AppDbContext>(...);
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddMediatR(...);

Benefits

  • Domain framework-free → ব্যবসার rule শুধু Domain-এ
  • Easy unit test — Mock repository
  • EF Core → Dapper swap → শুধু Infrastructure বদলান