六边形架构在C#中以Domain/Application为核心,通过Core层定义端口接口、Infrastructure层实现适配器、WebApi层依赖注入,确保业务代码零技术框架依赖。

六边形架构在 C# 中的核心结构怎么组织
六边形架构(Hexagonal Architecture)本质是把业务逻辑放在中心,所有外部依赖(数据库、HTTP、消息队列等)都通过「端口」抽象进来,再由「适配器」实现。C# 里不靠框架强制,而是靠接口 + 依赖注入 + 分层目录来落地。
关键不是命名是否叫“Port”或“Adapter”,而是能否做到:业务代码(Domain / Application 层)完全不引用 Microsoft.EntityFrameworkCore、Microsoft.AspNetCore.Mvc 或任何具体技术库。
- 定义端口:用
interface描述能力,比如IUserRepository、INotificationService,放在独立类库(如MyApp.Core)中 - 实现适配器:在另一个类库(如
MyApp.Infrastructure)中提供EntityFrameworkUserRepository或SmtpNotificationService - 依赖注入绑定:在宿主项目(如 Web API 的
Program.cs)中用services.AddScoped<IUserRepository, EntityFrameworkUserRepository>()
如何避免常见依赖泄漏(尤其是 Entity Framework)
最常破防的地方是 Domain 层悄悄引用了 EF 的类型,比如 DbContext、DbSet<T>,或直接在实体类里写 [Required] 这类数据注解——这等于把持久化细节拖进了核心。
正确做法是让实体保持“干净”:无属性装饰、无 EF 相关 using、不继承 BaseEntity(除非它本身也不带 EF 依赖)。
- 数据注解必须只出现在 Infrastructure 层的
EntityTypeConfiguration或 Fluent API 中,例如builder.Property(x => x.Email).IsRequired() - 查询参数对象(DTO)和领域实体严格分离;不要让 API Controller 直接接收
User实体,而应接收CreateUserRequest - 如果用了 CQRS,命令/查询的处理逻辑也应在 Application 层,不能把
IQueryable<User>暴露到外层
C# 中端口与适配器的典型命名与位置实践
命名不是重点,但混乱的命名会加速架构退化。建议统一前缀+后缀,并用文件夹物理隔离。
例如:
src/ ├── MyApp.Core/ // 纯接口 + 领域模型 │ ├── Ports/ │ │ ├── IUserRepository.cs │ │ └── IEmailSender.cs │ └── Entities/ │ └── User.cs ├── MyApp.Application/ // UseCase、DTO、领域服务实现(无外部依赖) │ ├── Users/ │ │ ├── CreateUserCommand.cs │ │ └── CreateUserHandler.cs ├── MyApp.Infrastructure/ // 所有适配器实现 │ ├── Data/ │ │ ├── EntityFrameworkUserRepository.cs │ │ └── MyAppDbContext.cs │ └── Notifications/ │ └── SmtpEmailSender.cs └── MyApp.WebApi/ // 仅含 Controllers、Startup、Mapper 配置
注意:EntityFrameworkUserRepository 可以引用 MyApp.Core,但反过来绝对不行;MyApp.WebApi 引用 MyApp.Application 和 MyApp.Infrastructure,但不直接引用 MyApp.Core(它通过 Application 间接使用)。
为什么有时“看起来更复杂”,但值得坚持
初建时你会多写几个 interface 和空壳实现,测试要 mock 端口,CI 构建多一个类库。这些成本真实存在。
但一旦需要替换数据库(比如从 SQL Server 切到 Cosmos DB),或把 Web API 改成 gRPC + Blazor Server 混合部署,或给同一套业务加个 CLI 工具入口——你只需要新增适配器,不用动一行领域逻辑。
真正容易被忽略的是:端口的粒度。别一上来就定义 IOrderService 这种大而全的接口,优先按 Use Case 拆,比如 IOrderPlacementPort、IOrderStatusNotifier;否则适配器会越来越重,测试边界模糊,最终变成“假六边形”。










