clean architecture 翻车主因是误将其当作目录模板而非依赖约束;domain 必须纯 .net 类库、零 nuget 依赖,仅含实体、值对象、领域异常、仓储/领域服务接口,且接口定义在 domain、实现置于 infrastructure。

为什么直接套用 Clean Architecture 模板容易翻车
因为 Clean Architecture 不是目录结构模板,而是依赖方向约束。很多人照着 GitHub 上的示例建了 Domain、Application、Infrastructure、Web 四层,结果 Domain 里悄悄引用了 Microsoft.EntityFrameworkCore,或者 Application 直接 new 了一个 HttpClient —— 这已经违反了“内层不依赖外层”的核心规则。
关键判断点就一个:Domain 项目必须是纯 .NET Standard / .NET 6+ 类库,且 不能有任何 NuGet 包依赖(连 System.Text.Json 都不该有,除非目标框架不自带)。
Domain 层怎么写才真正“干净”
它只该包含:实体(Entity)、值对象(ValueObject)、领域异常(DomainException)、仓储接口(IRepository<t></t>)、领域服务接口(IDomainService)。所有类型都该是 public,但不暴露实现细节。
-
Product实体里不要写 EF Core 的[Key]或[Column]特性 - 仓储接口方法名用领域语言,比如
FindActiveBySku(string sku),而不是GetById(int id) - 别在
Domain里定义 DTO 或 ViewModel —— 它们属于表现层契约,不是领域概念 - 如果要用泛型仓储,接口定义在
Domain,实现放在Infrastructure;Domain里只保留IRepository<product></product>这种具体契约
Application 层如何避免变成“胶水层”
它负责编排领域逻辑,但不处理数据访问、序列化、HTTP 协议细节。常见错误是把 Mapper.Map() 或 JsonSerializer.Serialize() 塞进 CommandHandler。
- 所有输入输出都用
Request/Response类(如CreateProductCommand),它们是Application自己的 DTO,和Domain无关 - 领域服务通过构造函数注入,类型必须是
Domain中定义的接口,比如IProductDomainService - 事务控制放在
Application层(用IUnitOfWork接口),但实现(如 EF Core 的SaveChangesAsync)在Infrastructure - 不要在
Application里写try/catch (SqlException)—— 数据库异常应在Infrastructure转成领域异常再往上抛
Startup 和 DI 注入最容易漏掉的三件事
ASP.NET Core 6+ 项目中,Program.cs 是外层入口,它负责把 Infrastructure 和 Presentation 的实现绑定到 Domain 和 Application 定义的接口上。这里错一点,整个架构就“漏气”。
- 必须用
services.AddDbContext<appdbcontext>()</appdbcontext>,但AppDbContext类型只能出现在Infrastructure层,Program.cs里不能出现它的命名空间引用 - 仓储实现注册要匹配接口粒度:比如
services.AddScoped<iproductrepository productrepository>()</iproductrepository>,而不是一股脑扫typeof(IRepository) -
Application层的 MediatR 手动注册(AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateProductCommand).Assembly)))必须指向Application程序集,不能指向Web或Infrastructure
最常被忽略的是:所有跨层调用必须走接口,哪怕只有一个实现。没有接口的依赖(比如直接 new 一个 LoggerFactory)会立刻让 Domain 或 Application 对具体技术栈产生隐式耦合。









