实体最佳实践 & 约定

实体

每个聚合根也是一个实体, 所以这些规则对聚合根也是有效的, 除非聚合根的某些规则覆盖了它们.

  • 推荐领域层 中定义实体.

主构造函数

  • 推荐 定义一个 主构造函数 确保实体在创建时的有效性, 在代码中通过主构造函数创建实体的新实例.

  • 推荐 根据需求把主构造函数定义为 public,internalprotected internal . 如果它不是public的, 那么应该由领域服务来创建实体.

  • 推荐 总是在主构造函数中初始化子集合.
  • 不推荐 在主构造函数中生成 Guid 键, 应该将其做为参数获取, 在调用时推荐使用 IGuidGenerator 生成新的 Guid 值做为参数.

无参构造函数

  • 推荐 总是定义 protected 无参构造函数与ORM兼容.

引用

  • 推荐 总是通过 id 引用 其他聚合根, 不要将导航属性添加到其他聚合根中.

类的其他成员

  • 推荐 总是将属性与方法定义为 virtual (除了私有方法 ). 因为有些ORM和动态代理工具需要.
  • 推荐 保持实体在自身边界内始终 有效一致.
    • 推荐 使用 private,protected,internalprotected internal setter定义属性, 保护实体的一致性和有效性.
    • 推荐 定义 public, internalprotected internal (virtual)方法在必要时更改属性值(使用非public setters时).

聚合根

主键

  • 推荐 总是使用 Id 属性做为聚合根主键.
  • 不推荐 在聚合根中使用 复合主键.
  • 推荐 所有的聚合根都使用 Guid 类型 主键.

基类

  • 推荐 根据需求继承 AggregateRoot<TKey> 或以下一个审计类 (CreationAuditedAggregateRoot<TKey>, AuditedAggregateRoot<TKey>FullAuditedAggregateRoot<TKey>).

聚合边界

  • 推荐 聚合尽可能小. 大多数聚合只有原始属性, 不会有子集合. 把这些视为设计决策:
    • 加载和保存聚合的 性能内存 成本 (请记住,聚合通常是做为一个单独的单元被加载和保存的). 较大的聚合会消耗更多的CPU和内存.
    • 一致性 & 有效性 边界.

示例

聚合根

  1. public class Issue : FullAuditedAggregateRoot<Guid> //使用Guid作为键/标识符
  2. {
  3. public virtual string Title { get; private set; } //使用 SetTitle() 方法set
  4. public virtual string Text { get; set; } //可以直接set,null值也是允许的
  5. public virtual Guid? MilestoneId { get; set; } //引用其他聚合根
  6. public virtual bool IsClosed { get; private set; }
  7. public virtual IssueCloseReason? CloseReason { get; private set; } //一个枚举类型
  8. public virtual Collection<IssueLabel> Labels { get; protected set; } //子集合
  9. protected Issue()
  10. {
  11. /* 此构造函数是提供给ORM用来从数据库中获取实体.
  12. * - 无需初始化Labels集合
  13. 因为它会被来自数据库的值覆盖.
  14. - It's protected since proxying and deserialization tools
  15. 可能不适用于私有构造函数.
  16. */
  17. }
  18. //主构造函数
  19. public Issue(
  20. Guid id, //从调用代码中获取Guid值
  21. [NotNull] string title, //表示标题不能为空.
  22. string text = null,
  23. Guid? milestoneId = null) //可选参数
  24. {
  25. Id = id;
  26. Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //验证
  27. Text = text;
  28. MilestoneId = milestoneId;
  29. Labels = new Collection<IssueLabel>(); //总是初始化子集合
  30. }
  31. public virtual void SetTitle([NotNull] string title)
  32. {
  33. Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //验证
  34. }
  35. /* AddLabel和RemoveLabel方法管理Labels集合
  36. * 安全的方式(防止两次添加相同的标签) */
  37. public virtual void AddLabel(Guid labelId)
  38. {
  39. if (Labels.Any(l => l.LabelId == labelId))
  40. {
  41. return;
  42. }
  43. Labels.Add(new IssueLabel(Id, labelId));
  44. }
  45. public virtual void RemoveLabel(Guid labelId)
  46. {
  47. Labels.RemoveAll(l => l.LabelId == labelId);
  48. }
  49. /* Close和ReOpen方法可保护一致性
  50. * IsClosed 与 CloseReason 属性. */
  51. public virtual void Close(IssueCloseReason reason)
  52. {
  53. IsClosed = true;
  54. CloseReason = reason;
  55. }
  56. public virtual void ReOpen()
  57. {
  58. IsClosed = false;
  59. CloseReason = null;
  60. }
  61. }

实体

  1. public class IssueLabel : Entity
  2. {
  3. public virtual Guid IssueId { get; private set; }
  4. public virtual Guid LabelId { get; private set; }
  5. protected IssueLabel()
  6. {
  7. }
  8. public IssueLabel(Guid issueId, Guid labelId)
  9. {
  10. IssueId = issueId;
  11. LabelId = labelId;
  12. }
  13. }

参考文献