概述
silky 推荐以 DDD(领域驱动设计) 的分层思想组织微服务代码,将一个微服务划分为多个职责清晰的层(项目)。本文描述标准分层结构、各层职责、包依赖关系及命名约定。
根据服务复杂程度,可选择不同的分层方案:
| 场景 | 推荐方案 |
|---|---|
| 业务逻辑复杂、多聚合、跨服务调用频繁 | 标准 6 层 DDD 分层(本文主要内容) |
| 功能简单的 CRUD 类服务(字典、配置管理等) | 简化分层方案(3-4 层) |
| 纯流量入口、无业务逻辑 | 网关分层 |
解决方案结构
- 推荐 为每个微服务应用模块创建一个单独的解决方案(
.sln)。 - 推荐 将解决方案命名为
CompanyName.MicroServiceName,例如Silky.OrderService。 - 推荐 每个微服务应用作为分层项目(多个
.csproj)进行开发,各层职责清晰、依赖单向流动。
业务微服务的分层架构
下图展示了一个典型微服务应用模块各层(项目)及其依赖关系:
graph TD
Host["<b>Host</b><br/>CompanyName.MicroServiceName.Host"]
Application["<b>Application</b><br/>CompanyName.MicroServiceName.Application"]
Contracts["<b>Application.Contracts</b><br/>CompanyName.MicroServiceName.Application.Contracts"]
Domain["<b>Domain</b><br/>CompanyName.MicroServiceName.Domain"]
DomainShared["<b>Domain.Shared</b><br/>CompanyName.MicroServiceName.Domain.Shared"]
EFCore["<b>EntityFrameworkCore</b><br/>CompanyName.MicroServiceName.EntityFrameworkCore"]
OtherContracts["<b>[OtherService].Application.Contracts</b><br/>其他微服务接口包(NuGet)"]
Host --> Application
Host --> EFCore
Application --> Contracts
Application --> Domain
Contracts --> DomainShared
Domain --> DomainShared
Domain -.->|RPC 调用| OtherContracts
EFCore --> Domain
上图为标准 DDD 6 层结构,适合业务复杂的服务。简单 CRUD 服务可参考简化分层方案。
各层说明
1. 领域共享层(Domain.Shared)
项目命名:CompanyName.MicroServiceName.Domain.Shared
职责:
- 定义领域中的枚举、常量、值对象等轻量类型
- 作为跨微服务共享的基础类型包,可被其他微服务模块的应用接口层直接引用
规则:
- ✅ 可以包含:枚举、常量、值对象、自定义异常
- ❌ 不能包含:实体(Entity)、仓储(Repository)、领域服务、应用服务
- 此包可安全地发布为独立 NuGet 包,供其他微服务引用
示例:
// 订单状态枚举(在 Domain.Shared 中定义)
public enum OrderStatus
{
Pending = 0, // 待处理
Paid = 1, // 已支付
Shipped = 2, // 已发货
Completed = 3, // 已完成
Cancelled = 4, // 已取消
}
2. 应用接口层(Application.Contracts)
项目命名:CompanyName.MicroServiceName.Application.Contracts
职责:
- 定义应用服务接口(以
[ServiceRoute]标识),这是微服务对外暴露服务的核心 - 定义数据传输对象(DTO):输入参数(
Input)和输出结果(Output) - 作为独立程序集发布,供其他微服务模块引用以生成 RPC 动态代理
依赖:Domain.Shared
规则:
- ✅ 可以包含:服务接口(
[ServiceRoute])、DTO、[Authorize]/[AllowAnonymous]等特性声明 - ❌ 不能包含:业务逻辑实现、数据库访问、领域实体
关键设计:将应用接口层单独抽象为独立程序集并发布为 NuGet 包,其他微服务通过引用该包即可获得 RPC 代理能力,实现透明调用。
示例:
// 订单应用服务接口
[ServiceRoute]
[Authorize] // 整个服务默认需要认证
public interface IOrderAppService
{
/// <summary>获取订单详情</summary>
[HttpGet("{id:long}")]
[GetCachingIntercept("order:id:{id}")] // 缓存拦截
Task<OrderOutput> GetAsync(long id);
/// <summary>创建订单(跨服务调用,参与分布式事务)</summary>
[HttpPost]
[Transaction] // 参与 TCC 分布式事务
Task<OrderOutput> CreateAsync(CreateOrderInput input);
/// <summary>取消订单</summary>
[HttpPatch("{id:long}/cancel")]
[RemoveCachingIntercept(typeof(OrderOutput), "order:id:{id}")] // 移除缓存
Task CancelAsync(long id);
/// <summary>查询内部接口,禁止外部 HTTP 访问</summary>
[Governance(ProhibitExtranet = true)]
Task<OrderOutput> GetInternalAsync(long id);
}
3. 应用层(Application)
项目命名:CompanyName.MicroServiceName.Application
职责:
- 实现应用服务接口(
Application.Contracts中定义的接口) - 编排领域服务,协调跨聚合的业务流程
- 处理事务、权限校验、数据映射等横切关注点
依赖:Domain + Application.Contracts
规则:
- ✅ 可以包含:服务实现类、对象映射 Profile、事务标注
- ❌ 不能包含:直接访问数据库(应通过领域层仓储接口)、HTTP 相关逻辑
示例:
public class OrderAppService : IOrderAppService
{
private readonly IOrderDomainService _orderDomainService;
private readonly IInventoryAppService _inventoryAppService; // 其他微服务接口(RPC 代理)
public OrderAppService(
IOrderDomainService orderDomainService,
IInventoryAppService inventoryAppService)
{
_orderDomainService = orderDomainService;
_inventoryAppService = inventoryAppService;
}
public async Task<OrderOutput> GetAsync(long id)
{
var order = await _orderDomainService.GetByIdAsync(id);
return order.MapTo<OrderOutput>();
}
// TCC Try 阶段:预留资源
[TccTransaction(ConfirmMethod = nameof(CreateConfirm), CancelMethod = nameof(CreateCancel))]
public async Task<OrderOutput> CreateAsync(CreateOrderInput input)
{
// 通过 RPC 调用库存微服务,锁定库存(Try 阶段)
await _inventoryAppService.LockInventoryAsync(input.ProductId, input.Quantity);
var userId = (long)NullSession.Instance.UserId;
var order = await _orderDomainService.CreateAsync(input, userId);
return order.MapTo<OrderOutput>();
}
public async Task<OrderOutput> CreateConfirm(CreateOrderInput input)
{
// Confirm 阶段:正式扣减库存
await _inventoryAppService.DeductInventoryAsync(input.ProductId, input.Quantity);
return null;
}
public async Task<OrderOutput> CreateCancel(CreateOrderInput input)
{
// Cancel 阶段:释放锁定的库存
await _inventoryAppService.UnlockInventoryAsync(input.ProductId, input.Quantity);
return null;
}
public async Task CancelAsync(long id)
{
var userId = (long)NullSession.Instance.UserId;
await _orderDomainService.CancelAsync(id, userId);
}
[Governance(ProhibitExtranet = true)]
public async Task<OrderOutput> GetInternalAsync(long id)
{
var order = await _orderDomainService.GetByIdAsync(id);
return order.MapTo<OrderOutput>();
}
}
4. 领域层(Domain)
项目命名:CompanyName.MicroServiceName.Domain
职责:
- 定义领域实体(Entity)、聚合根(AggregateRoot)
- 定义仓储接口(
IOrderRepository) - 实现领域服务(封装核心业务逻辑)
依赖:Domain.Shared + 自身的 Application.Contracts(复用 DTO)+ 其他微服务的 Application.Contracts(需要 RPC 调用时按需引用)
实践说明:Domain 层依赖自身的
Application.Contracts是为了复用 DTO 而省去额外的映射层,属于实用主义取舍。若希望保持严格的层间隔离,可在 Domain 层定义独立的值对象,在 Application 层完成映射。
跨服务 RPC 调用推荐放在 Application 层(见服务间调用约定),Domain 层仅在业务规则确实需要外部数据时才引用外部接口。
规则:
- ✅ 可以包含:实体、聚合根、仓储接口、领域事件、领域服务
- ❌ 不能包含:数据库访问实现(在基础设施层)、HTTP 相关逻辑
示例:
// 订单聚合根
public class Order : Entity<long>
{
public string OrderNo { get; private set; }
public long UserId { get; private set; }
public decimal TotalAmount { get; private set; }
public OrderStatus Status { get; private set; }
public DateTime CreatedTime { get; private set; }
public ICollection<OrderItem> Items { get; private set; }
private Order() { }
public static Order Create(long userId, IEnumerable<OrderItem> items)
{
var order = new Order
{
OrderNo = GenerateOrderNo(),
UserId = userId,
Status = OrderStatus.Pending,
CreatedTime = DateTime.UtcNow,
Items = items.ToList()
};
order.TotalAmount = order.Items.Sum(i => i.Price * i.Quantity);
return order;
}
public void Cancel()
{
if (Status != OrderStatus.Pending)
throw new BusinessException("只有待处理的订单可以取消");
Status = OrderStatus.Cancelled;
}
private static string GenerateOrderNo()
=> $"ORD{DateTime.UtcNow:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
}
// 仓储接口
public interface IOrderRepository : IRepository<Order>
{
Task<Order> GetByOrderNoAsync(string orderNo);
Task<List<Order>> GetByUserIdAsync(long userId);
}
// 领域服务
public class OrderDomainService : IOrderDomainService, IScopedDependency
{
private readonly IOrderRepository _orderRepository;
public OrderDomainService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<Order> CreateAsync(CreateOrderInput input, long userId)
{
var items = input.Items.Select(i => new OrderItem(i.ProductId, i.Quantity, i.Price));
var order = Order.Create(userId, items);
return await _orderRepository.InsertNowAsync(order);
}
public async Task<Order> GetByIdAsync(long id)
{
var order = await _orderRepository.FindAsync(id);
if (order == null)
throw new EntityNotFoundException(typeof(Order), id);
return order;
}
public async Task CancelAsync(long id, long operatorId)
{
var order = await GetByIdAsync(id);
if (order.UserId != operatorId)
throw new AuthorizationException("无权取消他人的订单");
order.Cancel();
await _orderRepository.UpdateNowAsync(order);
}
}
5. 基础设施层(EntityFrameworkCore)
项目命名:CompanyName.MicroServiceName.EntityFrameworkCore
职责:
- 实现仓储接口(基于 EF Core 或其他 ORM)
- 配置
DbContext,定义实体与数据库表的映射关系 - 实现数据库迁移(单独建立迁移项目)
依赖:Domain
规则:
- ✅ 可以包含:DbContext、仓储实现、EFCore 实体配置(
IEntityTypeConfiguration<T>) - ❌ 不能被其他层(除 Host 层外)直接依赖
示例:
// DbContext 定义
public class OrderDbContext : AbstractDbContext<OrderDbContext>
{
public OrderDbContext(DbContextOptions<OrderDbContext> options) : base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
}
// 实体配置
public class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("Orders");
builder.Property(o => o.OrderNo).HasMaxLength(32).IsRequired();
builder.Property(o => o.TotalAmount).HasPrecision(18, 2);
builder.HasMany(o => o.Items).WithOne().HasForeignKey("OrderId");
}
}
// 仓储实现
public class OrderRepository : EFCoreRepository<Order>, IOrderRepository, IScopedDependency
{
public async Task<Order> GetByOrderNoAsync(string orderNo)
{
return await Entities.FirstOrDefaultAsync(o => o.OrderNo == orderNo);
}
public async Task<List<Order>> GetByUserIdAsync(long userId)
{
return await Entities
.Where(o => o.UserId == userId)
.OrderByDescending(o => o.CreatedTime)
.ToListAsync();
}
}
6. 主机层(Host)
项目命名:CompanyName.MicroServiceNameHost
职责:
- 作为应用的入口点,负责主机的构建和启动
- 注册所有需要的服务(通过
IConfigureService) - 配置日志、链路追踪等基础设施
- 必须依赖
Application层,否则服务条目不会注册到服务注册中心
依赖:Application + EntityFrameworkCore
示例:
// Program.cs
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureSilkyGeneralHostDefaults(options =>
{
options.ApplicationName = "OrderService";
})
.UseSerilogDefault(); // 使用 Serilog 日志
await hostBuilder.Build().RunAsync();
// ConfigureService.cs:注册所有额外服务
public class ConfigureService : IConfigureService
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// 注册 EFCore 数据库
services.AddDatabaseAccessor(options =>
{
options.AddDbPool<OrderDbContext>(DbProvider.MySql);
}, "OrderService.Database.Migrations");
// 注册 SkyAPM 链路追踪
services.AddSilkySkyApm();
// 注册消息总线(可选)
services.AddMassTransit(x =>
{
x.UsingRabbitMq((ctx, cfg) =>
{
cfg.Host(configuration["RabbitMq:Host"]);
});
});
}
public int Order => 1;
}
网关应用的分层
网关作为微服务集群的统一流量入口,只对外提供 HTTP 服务,不实现具体业务逻辑。
graph TD
GatewayHost["<b>Gateway.Host</b><br/>CompanyName.Gateway.Host<br/><small>AddSilkyHttpCore / AddSwaggerDocuments / AddSilkyIdentity</small>"]
Registry[("注册中心<br/>Consul / Nacos / Zookeeper")]
Contracts["<b>[ServiceX].Application.Contracts</b><br/>其他微服务接口包(可选,仅接口代理时引用)"]
GatewayHost -->|自动发现路由 & Swagger| Registry
GatewayHost -.->|接口代理直接调用时引用| Contracts
v3.x 特性:网关无需逐个引用各微服务的
Application.Contracts包,只需与业务微服务接入同一服务注册中心,即可自动获取所有服务的路由信息和 Swagger 文档。开发者仅在需要通过接口代理直接调用的场景下才引用目标微服务的Application.Contracts包。
网关 Host 层
// Program.cs
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureSilkyGatewayDefaults(webBuilder => webBuilder.UseStartup<Startup>())
.UseSerilogDefault();
await hostBuilder.Build().RunAsync();
// Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddSilkyHttpCore() // 必须
.AddRouting()
.AddSwaggerDocuments() // 聚合所有微服务的 Swagger
.AddSilkyIdentity(); // 统一 JWT 认证
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwaggerDocuments();
}
app.UseRouting();
app.UseSilkyIdentity();
app.UseSilkyWebSocketsProxy(); // WebSocket 代理(可选)
app.UseEndpoints(endpoints =>
{
endpoints.MapSilkyRpcServices();
endpoints.MapSilkyDashboardServices(); // Dashboard 管理端(可选)
});
}
}
项目依赖关系总览
Host
├── Application
│ ├── Application.Contracts
│ │ └── Domain.Shared
│ └── Domain
│ ├── Domain.Shared
│ ├── Application.Contracts(自身,为使用 DTO)
│ └── [OtherService].Application.Contracts(其他微服务接口,通过 RPC 调用)
└── EntityFrameworkCore
└── Domain
服务间调用约定
方式一:接口代理(推荐)
引用目标微服务的 Application.Contracts NuGet 包,通过构造注入接口即可实现透明 RPC 调用。框架自动完成服务发现、负载均衡、分布式事务传播。
跨服务调用推荐放在 Application 层,由应用层统一编排多个服务的协作,Domain 层只负责本服务的核心业务规则:
// ✅ 推荐:在 Application 层编排跨服务调用
public class OrderAppService : IOrderAppService
{
private readonly IOrderDomainService _orderDomainService;
private readonly IInventoryAppService _inventoryAppService; // 库存微服务 RPC 代理
public OrderAppService(
IOrderDomainService orderDomainService,
IInventoryAppService inventoryAppService)
{
_orderDomainService = orderDomainService;
_inventoryAppService = inventoryAppService;
}
// TCC Try 阶段:预留资源
[TccTransaction(ConfirmMethod = nameof(CreateConfirm), CancelMethod = nameof(CreateCancel))]
public async Task<OrderOutput> CreateAsync(CreateOrderInput input)
{
// Application 层先调用外部服务锁定库存(TCC Try)
await _inventoryAppService.LockInventoryAsync(input.ProductId, input.Quantity);
// 再调用领域服务创建订单
var userId = (long)NullSession.Instance.UserId;
var order = await _orderDomainService.CreateAsync(input, userId);
return order.MapTo<OrderOutput>();
}
public async Task CreateConfirm(CreateOrderInput input)
=> await _inventoryAppService.DeductInventoryAsync(input.ProductId, input.Quantity);
public async Task CreateCancel(CreateOrderInput input)
=> await _inventoryAppService.UnlockInventoryAsync(input.ProductId, input.Quantity);
}
当业务规则确实需要跨服务数据时(如在领域约束中校验外部数据),Domain 层也可引用外部接口,但需评估是否会让领域层承担过多基础设施职责。
方式二:模板调用(解耦场景)
不引用目标微服务的接口包,通过 IInvokeTemplate 以服务条目 ID 或 WebAPI 路径发起调用(适合网关层或动态路由场景,注意不支持分布式事务):
public class GatewayAggregateService : IGatewayAggregateService, IScopedDependency
{
private readonly IInvokeTemplate _invokeTemplate;
public GatewayAggregateService(IInvokeTemplate invokeTemplate)
{
_invokeTemplate = invokeTemplate;
}
// 通过服务条目 ID 调用 —— 格式:{命名空间}.{接口名}.{方法名}.{参数名}_{HTTP方法}
public async Task<OrderOutput> GetOrderByEntryIdAsync(long id)
{
return await _invokeTemplate.InvokeForObjectByServiceEntryId<OrderOutput>(
"Silky.OrderService.Application.Contracts.IOrderAppService.GetAsync.id_Get",
id);
}
// 通过 WebAPI 路径调用(GET)—— 路径与 Swagger 文档路由一致
public async Task<OrderOutput> GetOrderAsync(long id)
{
return await _invokeTemplate.GetForObjectAsync<OrderOutput>("api/order/{id}", id);
}
// 通过 WebAPI 路径调用(POST)—— 请求体支持匿名对象或字典
public async Task<OrderOutput> CreateOrderAsync(long productId, int quantity)
{
return await _invokeTemplate.PostForObjectAsync<OrderOutput>(
"api/order",
new { productId, quantity });
}
}
选择建议:WebAPI 路径调用(
GetForObjectAsync、PostForObjectAsync等)直观易读,路径与 Swagger 文档保持一致;服务条目 ID 调用(InvokeForObjectByServiceEntryId)标识更稳定,适合路由规则可能变更的场景。
两种方式均不支持分布式事务,有事务需求请使用方式一(接口代理)。
命名约定
| 项目类型 | 命名规范 | 示例 |
|---|---|---|
| 解决方案 | CompanyName.ServiceName | Silky.OrderService |
| 领域共享层 | *.Domain.Shared | Silky.OrderService.Domain.Shared |
| 应用接口层 | *.Application.Contracts | Silky.OrderService.Application.Contracts |
| 应用层 | *.Application | Silky.OrderService.Application |
| 领域层 | *.Domain | Silky.OrderService.Domain |
| 基础设施层 | *.EntityFrameworkCore | Silky.OrderService.EntityFrameworkCore |
| 数据库迁移 | *.Database.Migrations | Silky.OrderService.Database.Migrations |
| 主机层 | *Host | Silky.OrderServiceHost |
| 网关主机 | *GatewayHost | Silky.GatewayHost |
| 应用服务接口 | I{Name}AppService | IOrderAppService |
| 应用服务实现 | {Name}AppService | OrderAppService |
| 领域服务接口 | I{Name}DomainService | IOrderDomainService |
| 仓储接口 | I{Name}Repository | IOrderRepository |
| DTO 输入 | {Action}{Name}Input | CreateOrderInput |
| DTO 输出 | {Name}Output | OrderOutput |
项目内部文件夹结构
各层项目建议按业务聚合组织目录,而非按技术类型(避免单一的 Services/、Dtos/ 文件夹):
Silky.OrderService.Application.Contracts/
└── Orders/
├── IOrderAppService.cs # 应用服务接口
└── Dtos/
├── CreateOrderInput.cs
├── UpdateOrderInput.cs
└── OrderOutput.cs
Silky.OrderService.Application/
└── Orders/
├── OrderAppService.cs # 接口实现
└── Mappers/
└── OrderProfile.cs # AutoMapper / Mapster Profile
Silky.OrderService.Domain/
└── Orders/
├── Order.cs # 聚合根
├── OrderItem.cs # 实体
├── IOrderRepository.cs # 仓储接口
└── OrderDomainService.cs # 领域服务
Silky.OrderService.EntityFrameworkCore/
└── Orders/
├── OrderRepository.cs # 仓储实现
└── Configurations/
└── OrderEntityTypeConfiguration.cs
简化分层方案
对于功能单一、业务逻辑简单的服务(典型:字典管理、系统配置、操作日志查询等 CRUD 密集型服务),可以使用简化的 3-4 层结构:
graph TD
Host["<b>Host</b><br/>CompanyName.ServiceName.Host"]
Application["<b>Application</b><br/>CompanyName.ServiceName.Application<br/><small>接口 + DTO + 实现 + 实体 + 仓储接口</small>"]
EFCore["<b>EntityFrameworkCore</b><br/>CompanyName.ServiceName.EntityFrameworkCore<br/><small>DbContext + 仓储实现</small>"]
Host --> Application
Host --> EFCore
EFCore --> Application
| 简化方式 | 适用场景 | 注意事项 |
|---|---|---|
省略 Domain.Shared,枚举/常量放入 Application.Contracts | 无需对外共享基础类型 | 若后续其他服务需要引用这些类型,需拆分 |
合并 Domain 到 Application,实体和仓储接口都放在 Application | 无复杂领域逻辑 | 业务复杂后难以重构,需预判 |
省略独立的 Application.Contracts,接口直接定义在 Application | 该服务无需被其他微服务 RPC 调用 | 一旦需要对外暴露 RPC 接口,必须拆分 |
重要:只要该服务需要被其他微服务通过 RPC 代理调用,必须将接口和 DTO 抽离为独立的
Application.Contracts项目发布 NuGet 包。网关通过注册中心自动路由不需要此包,但 RPC 代理调用方必须引用。
常见误区
❌ 在 Application.Contracts 中写业务实现
Application.Contracts 是对外公开的接口契约,只应包含接口定义和 DTO,不能包含任何业务实现代码或 EF Core 依赖。
❌ 将领域实体直接作为接口返回类型
领域实体(Entity、AggregateRoot)包含业务行为和私有状态,不应直接暴露给外部。应在 Application.Contracts 中定义 DTO(Output)并通过对象映射转换后返回。
❌ 在多个微服务中各自定义相同的枚举或常量
跨微服务共享的枚举/常量应定义在 Domain.Shared 并作为独立 NuGet 包发布,避免各自维护导致的不一致。
❌ EntityFrameworkCore 层被 Application 或 Domain 层直接引用
依赖方向必须是:EntityFrameworkCore → Domain。Application 层通过仓储接口操作数据,不能直接注入 DbContext(Host 层负责注册 DbContext 除外)。
❌ 对所有服务强制套用完整 6 层结构
对于简单的数据管理类服务,合并 Domain 和 Application 层、省略 Domain.Shared 是合理的工程权衡。参见简化分层方案。
❌ 跨服务 RPC 调用全部写在 Domain 层
Domain 层应尽量保持对外部服务的最小依赖。涉及多个外部服务的调用应去到 Application 层,Domain 层专注本服务的核心业务规则。
