DDD架构模板Ncp.CleanDDD前言为什么我们需要一个干净的DDD模板在微服务和领域驱动设计DDDDomain-Driven Design越来越流行的今天很多团队在落地DDD时都会遇到一个共同的痛点代码架构混乱、职责不清、聚合根和值对象混在一起导致“骨架”不牢固最终变成了“泥球架构”。为了解决这个问题我设计并开源了一个轻量级、可落地的DDD架构模板——Ncp.CleanDDD。它基于Clean Architecture整洁架构的思想把DDD的核心概念实体、值对象、聚合、仓储、领域服务、应用服务清晰地分层并通过代码模板让开发者“开箱即用”。本文将通过通俗易懂的语言和代码示例带你理解Ncp.CleanDDD的设计理念并学会如何使用它构建一个高内聚、低耦合的领域驱动系统。## 什么是Ncp.CleanDDDNcp.CleanDDD 是一套基于 .NET 的DDD架构模板它的核心设计原则是-领域层独立所有业务逻辑、领域规则都封装在领域层不依赖任何基础设施数据库、消息队列等。-应用层薄如纸应用层只负责协调领域对象和基础设施不写业务逻辑。-依赖倒置高层模块领域层不依赖低层模块基础设施层两者都依赖抽象接口。你可以把它想象成一套“乐高积木”每一块积木层都有明确的职责积木之间通过接口抽象连接替换任何一块积木都不会影响其他部分。## 架构分层一览Ncp.CleanDDD 把项目分为四个核心层1.Domain领域层实体、值对象、聚合根、领域服务、仓储接口。2.Application应用层应用服务、DTO数据传输对象、接口适配器。3.Infrastructure基础设施层仓储实现、数据库上下文、外部服务调用。4.Presentation表现层API控制器、MVC视图、GraphQL端点。这种分层让业务逻辑像“洋葱”一样被保护在最中心外层只能通过接口与内层交互。## 实战构建一个订单系统让我们以一个“创建订单”的场景为例看看Ncp.CleanDDD如何落地。### 第一步定义领域层Domain领域层是系统的核心我们定义Order订单聚合根和OrderItem订单项值对象。csharp// 文件Domain/Aggregates/Order.csusing System;using System.Collections.Generic;using System.Linq;namespace Ncp.CleanDDD.Domain.Aggregates{ // 订单聚合根包含订单的基本信息和订单项列表 public class Order { public Guid Id { get; private set; } // 唯一标识 public string CustomerName { get; private set; } // 客户名称 public DateTime OrderDate { get; private set; } // 下单时间 public decimal TotalAmount { get; private set; } // 总金额 public ListOrderItem Items { get; private set; } // 订单项值对象集合 // 构造函数确保订单创建时必须包含客户名称 public Order(string customerName) { if (string.IsNullOrWhiteSpace(customerName)) throw new ArgumentException(客户名称不能为空); Id Guid.NewGuid(); CustomerName customerName; OrderDate DateTime.UtcNow; Items new ListOrderItem(); } // 添加订单项的方法封装业务规则比如不能添加金额为0的商品 public void AddItem(string productName, decimal unitPrice, int quantity) { if (quantity 0) throw new ArgumentException(数量必须大于0); if (unitPrice 0) throw new ArgumentException(单价必须大于0); var item new OrderItem(productName, unitPrice, quantity); Items.Add(item); RecalculateTotal(); // 每次添加后重新计算总金额 } // 私有方法重新计算总金额确保业务一致性 private void RecalculateTotal() { TotalAmount Items.Sum(i i.UnitPrice * i.Quantity); } } // 值对象订单项不可变仅通过属性比较相等性 public class OrderItem { public string ProductName { get; } public decimal UnitPrice { get; } public int Quantity { get; } public OrderItem(string productName, decimal unitPrice, int quantity) { ProductName productName ?? throw new ArgumentNullException(nameof(productName)); UnitPrice unitPrice; Quantity quantity; } // 值对象比较基于所有属性 public override bool Equals(object obj) { if (obj is OrderItem other) return ProductName other.ProductName UnitPrice other.UnitPrice Quantity other.Quantity; return false; } public override int GetHashCode() { return HashCode.Combine(ProductName, UnitPrice, Quantity); } }}关键点订单聚合根通过AddItem方法封装了业务规则如数量必须大于0并且自动维护TotalAmount的一致性。这体现了DDD的“充血模型”——不是简单的数据容器。### 第二步定义仓储接口和领域服务仓储接口定义在领域层让应用层和基础设施层都依赖这个抽象。csharp// 文件Domain/Repositories/IOrderRepository.csusing System;using System.Threading.Tasks;using Ncp.CleanDDD.Domain.Aggregates;namespace Ncp.CleanDDD.Domain.Repositories{ // 仓储接口定义对订单聚合根的持久化操作 public interface IOrderRepository { TaskOrder GetByIdAsync(Guid id); // 根据ID获取订单 Task AddAsync(Order order); // 添加新订单 Task UpdateAsync(Order order); // 更新订单 Task DeleteAsync(Guid id); // 删除订单 }}同时如果需要跨聚合的业务逻辑我们会在领域层定义领域服务。例如检查客户信用额度csharp// 文件Domain/Services/ICustomerCreditService.csusing System.Threading.Tasks;namespace Ncp.CleanDDD.Domain.Services{ public interface ICustomerCreditService { Taskbool HasEnoughCredit(string customerName, decimal amount); }}### 第三步实现基础设施层基础设施层实现仓储接口这里我们用 EF Core 作为 ORM 示例。csharp// 文件Infrastructure/Repositories/OrderRepository.csusing System;using System.Threading.Tasks;using Microsoft.EntityFrameworkCore;using Ncp.CleanDDD.Domain.Aggregates;using Ncp.CleanDDD.Domain.Repositories;namespace Ncp.CleanDDD.Infrastructure.Repositories{ public class OrderRepository : IOrderRepository { private readonly AppDbContext _context; public OrderRepository(AppDbContext context) { _context context; } public async TaskOrder GetByIdAsync(Guid id) { // 从数据库加载订单并包含订单项值对象 return await _context.Orders .Include(o o.Items) // 注意这里Items是值对象但EF Core需要配置 .FirstOrDefaultAsync(o o.Id id); } public async Task AddAsync(Order order) { await _context.Orders.AddAsync(order); await _context.SaveChangesAsync(); } public async Task UpdateAsync(Order order) { _context.Orders.Update(order); await _context.SaveChangesAsync(); } public async Task DeleteAsync(Guid id) { var order await _context.Orders.FindAsync(id); if (order ! null) { _context.Orders.Remove(order); await _context.SaveChangesAsync(); } } }}注意值对象OrderItem在EF Core中通常需要配置为“拥有类型”Owned Entity以保证它不会单独存在而是作为订单的一部分。### 第四步编写应用层Application应用层是“胶水代码”它接收输入调用领域对象和仓储然后返回结果。csharp// 文件Application/Services/OrderService.csusing System;using System.Threading.Tasks;using Ncp.CleanDDD.Domain.Aggregates;using Ncp.CleanDDD.Domain.Repositories;using Ncp.CleanDDD.Domain.Services;namespace Ncp.CleanDDD.Application.Services{ public class OrderService { private readonly IOrderRepository _orderRepository; private readonly ICustomerCreditService _creditService; // 通过依赖注入获取仓储和领域服务 public OrderService(IOrderRepository orderRepository, ICustomerCreditService creditService) { _orderRepository orderRepository; _creditService creditService; } // 创建订单的应用服务方法 public async TaskGuid CreateOrderAsync(string customerName, string productName, decimal unitPrice, int quantity) { // 1. 调用领域服务检查信用额度 var hasCredit await _creditService.HasEnoughCredit(customerName, unitPrice * quantity); if (!hasCredit) throw new InvalidOperationException(客户信用额度不足); // 2. 创建订单聚合根领域逻辑自动处理 var order new Order(customerName); order.AddItem(productName, unitPrice, quantity); // 3. 持久化到仓储 await _orderRepository.AddAsync(order); // 4. 返回新订单ID return order.Id; } }}应用层的工作流 验证输入 → 调用领域服务 → 操作聚合根 → 调用仓储 → 返回结果。 它不包含任何业务规则如“数量必须大于0”这些都在领域层中。### 第五步表现层API控制器最后在表现层中暴露一个REST API端点。csharp// 文件Presentation/Controllers/OrderController.csusing Microsoft.AspNetCore.Mvc;using System;using System.Threading.Tasks;using Ncp.CleanDDD.Application.Services;[ApiController][Route(api/[controller])]public class OrderController : ControllerBase{ private readonly OrderService _orderService; public OrderController(OrderService orderService) { _orderService orderService; } [HttpPost] public async TaskIActionResult CreateOrder([FromBody] CreateOrderRequest request) { try { var orderId await _orderService.CreateOrderAsync( request.CustomerName, request.ProductName, request.UnitPrice, request.Quantity ); return Ok(new { OrderId orderId }); } catch (Exception ex) { return BadRequest(new { Error ex.Message }); } }}public class CreateOrderRequest{ public string CustomerName { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } public int Quantity { get; set; }}## 模板的优势与最佳实践使用Ncp.CleanDDD模板后你会获得以下好处1.可测试性领域层不依赖基础设施可以轻松进行单元测试。2.可维护性业务规则集中在领域层修改时不会影响其他层。3.可扩展性替换数据库或消息队列时只需修改基础设施层。4.团队协作分层清晰前端、后端、领域专家各司其职。最佳实践建议- 保持领域层“零依赖”不引用任何第三方库。- 应用层使用DTO数据传输对象避免领域对象泄露到表现层。- 对于复杂的领域逻辑优先使用领域服务而不是在聚合根中塞入过多方法。## 总结Ncp.CleanDDD 不仅仅是一个代码模板更是一种思维方式。它强迫开发者把业务逻辑放在正确的位置用“充血模型”替代“贫血模型”用“依赖倒置”打破层与层之间的硬性耦合。当你面对一个复杂的业务系统时这种架构会让你像剥洋葱一样一层层地看清问题的本质。如果你正在寻找一个既能快速上手、又能长期维护的DDD落地模板不妨试试 Ncp.CleanDDD。它不会替你写业务代码但会帮你把代码组织得井井有条。现在就去 GitHub 上搜索Ncp.CleanDDD下载模板开始你的领域驱动设计之旅吧