DDD 领域驱动设计指南
FreeKitModules 采用 DDD(领域驱动设计)架构,提供完整的分层实现。
架构分层
┌─────────────────────────────────────────┐
│ Controllers / API │ ← 接口层
├─────────────────────────────────────────┤
│ Application Services │ ← 应用层
├─────────────────────────────────────────┤
│ Domain Services / Entities / Events │ ← 领域层
├─────────────────────────────────────────┤
│ Infrastructure (FreeSql, Redis, etc.) │ ← 基础设施层
└─────────────────────────────────────────┘
实体设计
基类选择
| 基类 | 适用场景 |
|---|---|
Entity<Guid> | 只需要主键 |
Entity<Guid>, ISoftDelete | 需要软删除 |
FullAuditEntity<Guid, Guid> | 完整审计(推荐) |
示例:多租户文章
using IGeekFan.FreeKit.Extras.AuditEntity;
public class Article : FullAuditEntity<Guid, Guid>, ITenant
{
public Guid? TenantId { get; set; }
public string Title { get; set; } = default!;
public string Content { get; set; } = default!;
}
自动填充字段
继承 FullAuditEntity 后自动获得:
| 字段 | 说明 |
|---|---|
Id | Guid 主键 |
CreateUserId / CreateTime | 创建人/时间 |
UpdateUserId / UpdateUserName / UpdateTime | 修改人/时间 |
DeleteUserId / DeleteUserName / DeleteTime | 删除人/时间 |
IsDeleted | 软删除标记 |
TenantId | 租户 ID |
领域事件
定义事件
public sealed record ArticlePublishedEvent(Guid ArticleId) : IDomainEvent;
触发事件
public sealed class Article : DomainEventBase
{
public Guid Id { get; set; }
public void Publish()
{
AddDomainEvent(new ArticlePublishedEvent(Id));
}
public override object[] GetKeys() => [Id];
}
处理事件
public class ArticlePublishedHandler : INotificationHandler<ArticlePublishedEvent>
{
public Task Handle(ArticlePublishedEvent e, CancellationToken ct)
{
// 发送通知、更新统计等
return Task.CompletedTask;
}
}
仓储模式
定义仓储接口
public interface IArticleRepository : IBaseRepository<Article, Guid>
{
}
使用仓储
public class ArticleService : IScopedDependency
{
private readonly IArticleRepository _repo;
public ArticleService(IArticleRepository repo)
{
_repo = repo;
}
public async Task<Article> GetByIdAsync(Guid id)
{
return await _repo.FindAsync(id);
}
public async Task<List<Article>> GetListAsync()
{
return await _repo.Select.ToListAsync();
}
}
事务管理
使用事务
[Transactional]
public async Task TransferAsync(Guid fromId, Guid toId, decimal amount)
{
var from = await _repo.FindAsync(fromId);
var to = await _repo.FindAsync(toId);
from.Balance -= amount;
to.Balance += amount;
await _repo.UpdateAsync(from);
await _repo.UpdateAsync(to);
// 异常自动回滚
}
事务配置
| 特性 | 说明 |
|---|---|
[Transactional] | 默认新事务 |
[Transactional(IsDisabled = true)] | 禁用事务 |
[Transactional(Propagation = Propagation.ReQUIRES_NEW)] | 总是新事务 |
值对象
public class Address : ValueObject
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Street;
yield return City;
yield return ZipCode;
}
}
聚合根
public class Order : AggregateRootBase<Guid>
{
public Guid CustomerId { get; set; }
public List<OrderItem> Items { get; set; } = new();
public decimal TotalAmount => Items.Sum(i => i.Price * i.Quantity);
public void AddItem(Guid productId, int quantity, decimal price)
{
Items.Add(new OrderItem
{
ProductId = productId,
Quantity = quantity,
Price = price
});
}
}
依赖注入标记
配合 Autofac 自动注册服务:
// 每次请求新实例
public class ArticleService : IScopedDependency
{
}
// 单例
public class CacheService : ISingletonDependency
{
}
// 瞬时
public class EmailSender : ITransientDependency
{
}
最佳实践
实体设计原则
- 单一职责:一个实体只负责一个业务概念
- 值对象优先:没有独立身份的属性用值对象
- 聚合边界:明确聚合根和聚合内实体的关系
- 领域事件:跨聚合通信使用领域事件
命名规范
| 层 | 接口 | 实现 | 示例 |
|---|---|---|---|
Domain/ | IXxxManager | XxxManager | IArticleManager / ArticleManager |
Application/ | IXxxService | XxxService | IArticleService / ArticleService |
Controllers/ | — | XxxController | ArticleController |