第三章:项目结构设计——解决方案组织、类库分层与共享内核
好,咱们进入第三章。这一章我打算聊聊项目结构设计。说实话,很多团队在微服务拆分初期,代码结构往往是一团乱麻。我见过不少项目,所有逻辑都塞在一个项目里,美其名曰“快速迭代”,结果三个月后连自己都看不懂了。
我个人习惯是:先定结构,再写代码。结构对了,后面的事情就顺了。今天咱们就围绕三个核心点展开:解决方案怎么组织、类库怎么分层、共享内核怎么设计。
3.1 解决方案组织:从单体到多服务的演进
先说说解决方案的组织方式。在 .NET Core 里,一个解决方案可以包含多个项目。微服务架构下,我建议每个服务独立一个解决方案。为什么?因为每个服务应该有自己的生命周期,独立部署、独立演进。
举个例子,我之前参与过一个电商平台的重构。最初所有功能都在一个解决方案里,订单、支付、库存、用户全混在一起。每次改订单的代码,都得把整个解决方案拉下来编译。后来我们拆成了 6 个独立的解决方案,每个服务一个,开发效率提升了不少。
那具体怎么组织呢?我一般这样搞:
- 每个微服务一个解决方案,命名如
OrderService.sln、PaymentService.sln - 解决方案内包含多个项目,按职责分层
- 共享的公共组件,单独放在一个 NuGet 包或 Git Submodule 里
你可能会问:“那服务之间怎么通信?”别急,后面章节会专门讲。这里先记住一点:结构清晰比什么都重要。
3.2 类库分层:API、Application、Domain、Infrastructure
接下来是重头戏——类库分层。我采用的是经典的 DDD 四层架构:API、Application、Domain、Infrastructure。这四层各司其职,互不越界。
| 层 | 职责 | 典型内容 |
|---|---|---|
| API | 对外暴露接口,处理 HTTP 请求/响应 | Controller、Filter、Middleware |
| Application | 编排业务逻辑,协调领域对象 | Service、DTO、Mapper |
| Domain | 核心业务规则,不依赖外部 | Entity、ValueObject、AggregateRoot |
| Infrastructure | 技术实现,如数据库、消息队列 | Repository、DbContext、第三方SDK |
嗯,这里要注意一个关键点:依赖方向是向内的。API 层依赖 Application 层,Application 层依赖 Domain 层,Infrastructure 层也依赖 Domain 层。Domain 层是核心,不依赖任何外部框架。
我在项目中遇到过一个问题:有人把数据库查询逻辑直接写在 Controller 里。当时我一看,血压就上来了。你想想看,如果将来换数据库,是不是得改所有 Controller?这就是分层没做好的后果。
正确的做法是:Controller 只做参数校验和结果返回,真正的业务逻辑交给 Application 层。Domain 层只关心业务规则,不关心数据怎么存、消息怎么发。
3.2.1 API 层
API 层说白了就是“门面”。它负责接收 HTTP 请求,调用 Application 层的服务,然后返回结果。我个人习惯在 API 层做两件事:
- 参数校验:用 FluentValidation 或 DataAnnotation 做输入验证
- 异常处理:用全局异常过滤器统一处理错误
代码示例:
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
private readonly IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
var result = await _orderService.CreateOrderAsync(request);
return Ok(result);
}
}
你看,Controller 里没有业务逻辑,只是调用服务。这样测试起来也方便。
3.2.2 Application 层
Application 层是“导演”。它不直接写业务规则,而是协调 Domain 层的对象来完成工作。比如创建订单时,Application 层会调用 Order 聚合根的方法,然后通过 Repository 保存。
我曾经犯过一个错误:把业务规则写在 Application 层。比如“订单金额不能为负数”这种校验,我写在了 Service 里。后来 Domain 层变得很薄,业务逻辑散落在各处。嗯,这是个坑,大家别踩。
正确的做法是:业务规则放在 Domain 层,Application 层只做编排和事务管理。
3.2.3 Domain 层
Domain 层是“心脏”。所有核心业务逻辑都在这里。它包含实体、值对象、聚合根、领域服务等。Domain 层不依赖任何外部框架,连 EF Core 都不依赖。
举个例子:
public class Order : AggregateRoot
{
public Guid Id { get; private set; }
public decimal TotalAmount { get; private set; }
public OrderStatus Status { get; private set; }
public void AddItem(OrderItem item)
{
// 业务规则:订单项不能重复
if (_items.Any(i => i.ProductId == item.ProductId))
throw new DomainException("商品已存在");
_items.Add(item);
TotalAmount += item.Price * item.Quantity;
}
}
你看,业务规则封装在实体内部,外部只能通过方法调用。这样保证了数据的一致性和完整性。
3.2.4 Infrastructure 层
Infrastructure 层是“手脚”。它负责技术实现,比如数据库访问、消息发送、文件存储等。这一层依赖 Domain 层,实现 Domain 层定义的接口。
比如,Domain 层定义了一个 IOrderRepository 接口,Infrastructure 层用 EF Core 实现它:
public class OrderRepository : IOrderRepository
{
private readonly OrderDbContext _context;
public OrderRepository(OrderDbContext context)
{
_context = context;
}
public async Task<Order> GetByIdAsync(Guid id)
{
return await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id);
}
}
这样,如果将来要换数据库,只需要改 Infrastructure 层的实现,Domain 层完全不受影响。
3.3 共享内核:避免重复造轮子
最后聊聊共享内核。在微服务架构中,多个服务可能需要共用一些基础组件,比如:
- 通用的值对象(如 Address、Money)
- 基础类型(如 Maybe、Result)
- 工具类(如 时间处理、加密)
- 异常定义(如 DomainException)
我建议把这些公共代码提取到一个独立的类库中,命名为 SharedKernel。注意,共享内核应该只包含与业务无关的基础设施,不要放业务逻辑。
举个例子:
public class Money : ValueObject
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Amount;
yield return Currency;
}
}
这个 Money 值对象可以在订单服务、支付服务、库存服务中复用。避免了每个服务都自己实现一套“金额”逻辑。
3.4 避坑指南
讲到这里,我分享几个实际踩过的坑:
- 不要过度分层:有些团队搞了五六层,结果每层都在做 CRUD,反而增加了复杂度。四层架构够用了。
- 注意循环依赖:如果 A 层依赖 B 层,B 层又依赖 A 层,那代码就乱套了。用依赖注入和接口隔离可以避免。
- 共享内核要版本化:如果共享内核是 NuGet 包,记得用语义化版本控制。我曾经因为忘记更新版本号,导致不同服务用了不同版本的共享内核,出了不少 bug。
好了,这一章的内容就到这里。下一章咱们聊聊服务间通信,包括 HTTP、gRPC、消息队列等。到时候我会结合一个实际案例来讲,保证干货满满。