Difference from N-tier: in N-tier the BLL points to the DAL.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | // ====================================================================== // Q: Explain Onion / Clean Architecture. How do the projects reference each // other? // // - Difference from N-tier: in N-tier the BLL points to the DAL. Here // Infrastructure points to Application (dependency inversion). The database is // a detail. // - Onion vs Clean: practically the same. Clean explicitly names the use cases // (handlers) and the "interface adapters". // - Enforce the rules with architecture tests, so that nobody (not even an AI // agent) puts EF Core into the Domain. // ====================================================================== // Domain: plain C#, the rules live inside the entity public class Order { public OrderStatus Status { get; private set; } public void Cancel() { if (Status == OrderStatus.Shipped) throw new DomainException("Already shipped"); Status = OrderStatus.Cancelled; } } // Application: defines the interface (port) and the use case public interface IOrderRepository { Task<Order?> GetAsync(int id, CancellationToken ct); } public class CancelOrderHandler(IOrderRepository orders, IUnitOfWork uow) { public async Task Handle(int id, CancellationToken ct) { var order = await orders.GetAsync(id, ct) ?? throw new NotFoundException(); order.Cancel(); await uow.SaveChangesAsync(ct); } } // Infrastructure: the EF Core implementation public class EfOrderRepository(AppDbContext db) : IOrderRepository { public Task<Order?> GetAsync(int id, CancellationToken ct) => db.Orders.Include(o => o.Lines).FirstOrDefaultAsync(o => o.Id == id, ct); } // Api: composition root builder.Services.AddScoped<IOrderRepository, EfOrderRepository>(); // Architecture test: the Domain must not depend on EF Core or Infrastructure [Fact] // NetArchTest.Rules public void Domain_does_not_depend_on_infrastructure() { var result = Types.InAssembly(typeof(Order).Assembly) .ShouldNot().HaveDependencyOnAny("Microsoft.EntityFrameworkCore", "Shop.Infrastructure") .GetResult(); Assert.True(result.IsSuccessful); } |