用 DeepSeek 做领域驱动编码:DDD 四层架构实战
·
本文是《DeepSeek 辅助编程实战》系列第 3 篇,基于前两篇的需求和设计文档,演示如何用 DeepSeek 逐层实现 DDD 四层架构的后端代码。本文以 .NET Core (ASP.NET Core 8) 版本为例。
一、DDD 编码的核心原则
在开始编码之前,明确 DDD 的核心原则:
- 领域层不依赖任何框架:Entity、ValueObject、DomainService 都是纯业务代码
- 依赖倒置:领域层定义 Repository 接口,基础设施层实现
- 聚合根保护业务不变量:状态转换、业务校验都封装在聚合根内部
- 值对象不可变:使用 C# record 实现
二、逐层编码实战
第 1 层:领域层(Domain Layer)
2.1 枚举定义
我的 Prompt:
请基于以下状态机,用 C# 实现枚举和扩展方法:
状态:DRAFT → ISSUED → CHANGING → ISSUED(版本+1)
任意状态 → CANCELLED
要求:
1. 枚举类型 PlanStatus
2. 扩展方法 CanTransitionTo() 验证状态转换合法性
3. 枚举值带中文显示名称
DeepSeek 生成的代码:
// Enums/PlanStatus.cs
public enum PlanStatus
{
Draft = 0, // 草稿
Issued = 1, // 已发布
Changing = 2, // 变更中
Cancelled = 3 // 已取消
}
public static class PlanStatusExtensions
{
public static bool CanTransitionTo(this PlanStatus from, PlanStatus to)
{
return (from, to) switch
{
(PlanStatus.Draft, PlanStatus.Issued) => true,
(PlanStatus.Issued, PlanStatus.Changing) => true,
(PlanStatus.Changing, PlanStatus.Issued) => true,
(_, PlanStatus.Cancelled) => true,
_ => false
};
}
public static string GetDisplayName(this PlanStatus status)
{
return status switch
{
PlanStatus.Draft => "草稿",
PlanStatus.Issued => "已发布",
PlanStatus.Changing => "变更中",
PlanStatus.Cancelled => "已取消",
_ => status.ToString()
};
}
}
2.2 值对象
我的 Prompt:
请用 C# record 实现以下值对象:
1. Port(港口):unLocode(5位UN/LOCODE)、name、country、berth、
estimatedArrival、estimatedDeparture
- 构造函数校验 unLocode 格式(5位大写字母数字)
- 提供 CalculatePortStayDuration() 计算在港停留时间
2. RouteSegment(航线段):fromPortCode、toPortCode、distanceNauticalMiles、
estimatedSpeedKnots、estimatedDurationHours、sequence、
estimatedFuelConsumptionIFO、estimatedFuelConsumptionMGO
- 静态方法 CalculateFuelConsumption() 和 CalculateDuration()
使用 record 类型,确保不可变性。
DeepSeek 生成的 Port 值对象:
// ValueObjects/Port.cs
public record Port
{
public string UnLocode { get; }
public string Name { get; }
public string Country { get; }
public string? Berth { get; }
public DateTime? EstimatedArrival { get; }
public DateTime? EstimatedDeparture { get; }
public Port(string unLocode, string name, string country,
string? berth = null,
DateTime? estimatedArrival = null,
DateTime? estimatedDeparture = null)
{
if (string.IsNullOrWhiteSpace(unLocode) || unLocode.Length != 5)
throw new ArgumentException("UN/LOCODE 必须为5位字符");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("港口名称不能为空");
UnLocode = unLocode.ToUpper();
Name = name;
Country = country;
Berth = berth;
EstimatedArrival = estimatedArrival;
EstimatedDeparture = estimatedDeparture;
}
public double? CalculatePortStayDuration()
{
if (EstimatedArrival == null || EstimatedDeparture == null)
return null;
return (EstimatedDeparture.Value - EstimatedArrival.Value).TotalHours;
}
}
2.3 聚合根
我的 Prompt:
请实现 VoyagePlan 聚合根,要求:
1. 使用工厂方法 Create() 创建新计划
2. 状态转换方法:Issue()、InitiateChange()、CompleteChange()、Cancel()
3. CompleteChange() 时版本号+1,并克隆为新版本
4. 业务不变量:
- 状态转换必须符合状态机规则
- 发布前需校验完整性
5. 领域事件收集:每次状态变更记录 VoyagePlanEvent
6. 汇总计算:totalDistance 和 totalEstimatedHours 从航线段自动求和
用 C# 实现,包含完整的业务逻辑和校验。
DeepSeek 生成的聚合根(核心方法):
// Entities/VoyagePlan.cs
public class VoyagePlan
{
public long Id { get; private set; }
public string PlanNumber { get; private set; }
public string VoyageName { get; private set; }
public string VesselName { get; private set; }
public string VoyageNumber { get; private set; }
public PlanStatus Status { get; private set; }
public int Version { get; private set; }
// 值对象
public Port DeparturePort { get; private set; }
public Port DestinationPort { get; private set; }
public List<RouteSegment> RouteSegments { get; private set; }
public RiskAssessment RiskAssessment { get; private set; }
public FuelEstimate FuelEstimate { get; private set; }
public EmergencyPlan EmergencyPlan { get; private set; }
// 汇总
public decimal TotalDistance { get; private set; }
public decimal TotalEstimatedHours { get; private set; }
// 领域事件
private readonly List<VoyagePlanEvent> _domainEvents = new();
public IReadOnlyList<VoyagePlanEvent> DomainEvents => _domainEvents.AsReadOnly();
// 工厂方法
public static VoyagePlan Create(string voyageName, string vesselName,
string voyageNumber, Port departurePort, Port destinationPort,
List<RouteSegment> routeSegments, RiskAssessment riskAssessment,
FuelEstimate fuelEstimate, EmergencyPlan emergencyPlan,
string createdBy, string? remarks = null)
{
var plan = new VoyagePlan
{
VoyageName = voyageName,
VesselName = vesselName,
VoyageNumber = voyageNumber,
Status = PlanStatus.Draft,
Version = 1,
DeparturePort = departurePort,
DestinationPort = destinationPort,
RouteSegments = routeSegments,
RiskAssessment = riskAssessment,
FuelEstimate = fuelEstimate,
EmergencyPlan = emergencyPlan,
CreatedBy = createdBy,
Remarks = remarks,
CreatedAt = DateTime.UtcNow,
};
plan.CalculateTotals();
plan.RegisterEvent(VoyagePlanEventType.Created, createdBy, "创建航次计划");
return plan;
}
// 发布
public void Issue(string issuedBy)
{
EnsureStatus(PlanStatus.Draft);
EnsureComplete();
Status = PlanStatus.Issued;
IssuedAt = DateTime.UtcNow;
RegisterEvent(VoyagePlanEventType.Issued, issuedBy, "发布航次计划");
}
// 发起变更
public void InitiateChange(string changedBy, string changeReason)
{
EnsureStatus(PlanStatus.Issued);
Status = PlanStatus.Changing;
RegisterEvent(VoyagePlanEventType.ChangeInitiated, changedBy,
$"发起变更: {changeReason}");
}
// 完成变更 → 版本+1
public void CompleteChange(string changedBy)
{
EnsureStatus(PlanStatus.Changing);
Version++;
Status = PlanStatus.Issued;
RegisterEvent(VoyagePlanEventType.ChangeCompleted, changedBy,
$"完成变更,升级至版本 V{Version}");
}
// 取消
public void Cancel(string cancelledBy, string reason)
{
if (Status == PlanStatus.Cancelled)
throw new BusinessException("计划已取消,不可重复取消");
Status = PlanStatus.Cancelled;
RegisterEvent(VoyagePlanEventType.Cancelled, cancelledBy,
$"取消计划: {reason}");
}
private void EnsureStatus(PlanStatus expected)
{
if (Status != expected)
throw new BusinessException(
$"当前状态 {Status.GetDisplayName()} 不允许此操作,需要 {expected.GetDisplayName()} 状态");
}
private void EnsureComplete()
{
var missing = GetMissingFields();
if (missing.Count > 0)
throw new BusinessException($"计划不完整,缺少: {string.Join(", ", missing)}");
}
private void CalculateTotals()
{
TotalDistance = RouteSegments.Sum(s => s.DistanceNauticalMiles);
TotalEstimatedHours = RouteSegments.Sum(s => s.EstimatedDurationHours);
}
}
2.4 领域服务
我的 Prompt:
请实现 VoyagePlanDomainService,包含以下用例:
1. CreatePlanAsync:创建计划前检查船舶是否有活跃计划
2. IssuePlanAsync:发布计划
3. InitiateChangeAsync:发起变更
4. CompleteChangeAsync:完成变更
5. CancelPlanAsync:取消计划
领域服务负责协调聚合根和仓储,不包含业务逻辑。
// Services/VoyagePlanDomainService.cs
public class VoyagePlanDomainService
{
private readonly IVoyagePlanRepository _repository;
public async Task<VoyagePlan> CreatePlanAsync(/* 参数 */)
{
// 业务不变量:同一船舶只能有一个活跃计划
bool hasActive = await _repository.HasActivePlanForVesselAsync(vesselName);
if (hasActive)
throw new BusinessException("该船舶已有活跃的航次计划");
var plan = VoyagePlan.Create(/* ... */);
var planNumber = await _repository.GeneratePlanNumberAsync();
plan.SetPlanNumber(planNumber);
await _repository.CreateAsync(plan);
return plan;
}
}
第 2 层:应用层(Application Layer)
我的 Prompt:
请实现应用层,包含:
1. DTO 定义:CreateVoyagePlanRequest、VoyagePlanResponse、UpdateVoyagePlanRequest
2. 应用服务 VoyagePlanApplicationService:
- 11 个用例方法
- DTO ↔ Entity 转换
- 委托领域服务处理业务逻辑
- 统一异常处理(BusinessException → 400)
// Services/VoyagePlanApplicationService.cs
public class VoyagePlanApplicationService
{
private readonly IVoyagePlanRepository _repository;
private readonly VoyagePlanDomainService _domainService;
public async Task<VoyagePlanResponse> CreatePlanAsync(CreateVoyagePlanRequest request)
{
// DTO → 领域对象
var departurePort = new Port(request.DeparturePort.UnLocode, ...);
var routeSegments = request.RouteSegments.Select(s => new RouteSegment(...)).ToList();
// 委托领域服务
var plan = await _domainService.CreatePlanAsync(
request.VoyageName, request.VesselName, ...);
// Entity → DTO
return VoyagePlanResponse.FromEntity(plan);
}
}
第 3 层:基础设施层(Infrastructure Layer)
我的 Prompt:
请用 Entity Framework Core 实现:
1. VoyagePlanDbContext:使用 Fluent API 配置
- 值对象配置为 Owned 类型
- RouteSegments 和 TransitPorts 序列化为 JSON 列
- 忽略 DomainEvents(不持久化)
2. VoyagePlanRepository:实现 IVoyagePlanRepository
- 计划编号生成:VP-YYYYMMDD-NNN
- 活跃计划检查
// Persistence/VoyagePlanDbContext.cs
public class VoyagePlanDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<VoyagePlan>();
// 值对象配置为 Owned
entity.OwnsOne(p => p.DeparturePort);
entity.OwnsOne(p => p.DestinationPort);
entity.OwnsOne(p => p.RiskAssessment);
entity.OwnsOne(p => p.FuelEstimate);
entity.OwnsOne(p => p.EmergencyPlan);
// 集合值对象序列化为 JSON
entity.Property(p => p.RouteSegments)
.HasColumnType("text")
.HasConversion(
v => JsonSerializer.Serialize(v, _jsonOptions),
v => JsonSerializer.Deserialize<List<RouteSegment>>(v, _jsonOptions)!
);
// 忽略领域事件
entity.Ignore(p => p.DomainEvents);
}
}
第 4 层:API 层
我的 Prompt:
请实现 ASP.NET Core API 层:
1. Program.cs:DI 注册、中间件配置、Swagger
2. VoyagePlanController:11 个 Action
3. GlobalExceptionMiddleware:统一异常处理
要求:
- 使用 Minimal API 风格或 Controller 风格均可
- Swagger 文档分组
- CORS 配置
// Controllers/VoyagePlanController.cs
[ApiController]
[Route("api/v1/voyage-plans")]
public class VoyagePlanController : ControllerBase
{
private readonly VoyagePlanApplicationService _service;
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateVoyagePlanRequest request)
{
var result = await _service.CreatePlanAsync(request);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
[HttpPost("{id}/issue")]
public async Task<IActionResult> Issue(long id, [FromBody] IssueRequest request)
{
var result = await _service.IssuePlanAsync(id, request.IssuedBy);
return Ok(result);
}
}
三、Prompt 技巧
1. 按层逐步生成,不要一次生成全部代码
第 1 轮:领域层(枚举 → 值对象 → 聚合根 → 领域服务)
第 2 轮:应用层(DTO → 应用服务)
第 3 轮:基础设施层(DbContext → Repository)
第 4 轮:API 层(Controller → Middleware → DI)
2. 提供设计文档作为上下文
基于以下领域模型设计(见附录),请用 C# 实现聚合根。
[粘贴第 2 篇博客的领域模型输出]
3. 指定编码规范
要求:
- 使用 C# 12 语法特性(record、pattern matching)
- 属性使用 init-only setter
- 方法命名使用 PascalCase
- 私有字段使用 _camelCase
- 包含 XML 注释
4. 让 AI 生成单元测试
请为 VoyagePlanDomainService 编写单元测试,覆盖:
1. 创建计划成功
2. 创建计划失败(船舶有活跃计划)
3. 发布计划
4. 发起变更
5. 完成变更(版本+1)
6. 取消计划
使用 xUnit + Moq。
四、编码阶段的输出物
经过 4 轮编码对话,我们得到了:
- 领域层:3 个枚举 + 6 个值对象 + 1 个聚合根 + 1 个领域事件 + 1 个领域服务 + 1 个仓储接口
- 应用层:5 个 DTO + 1 个应用服务 + 1 个自定义异常
- 基础设施层:1 个 DbContext + 1 个 Repository 实现
- API 层:1 个 Controller + 1 个中间件 + DI 配置
- 测试:6 个单元测试用例
总计约 20+ 个 C# 文件,完整的 DDD 四层架构实现。
五、注意事项
- 代码审查不可省略:AI 生成的代码可能有编译错误或逻辑漏洞,需要逐行审查
- 编译验证:每生成一层代码就编译一次,及时发现类型不匹配等问题
- 测试驱动:先让 AI 写测试,再写实现,确保业务逻辑正确
下一篇预告
下一篇《用 DeepSeek 做前端开发》将演示如何用 DeepSeek 实现 Vue 3 + Element Plus 前端页面,包括列表页、详情页、创建表单和编辑表单。
更多推荐



所有评论(0)