【问题标题】:Managing persistence in DDD管理 DDD 中的持久性
【发布时间】:2012-12-06 03:42:48
【问题描述】:

假设我想创建一个博客应用程序,其中包含这两个与 EF Code First 或 NHibernate 一起使用并从存储库层返回的简单持久性类:

public class PostPersistence
{
   public int Id { get; set; }
   public string Text { get; set; }
   public IList<LikePersistence> Likes { get; set; }
}

public class LikePersistence
{
    public int Id { get; set; }
    //... some other properties
}

我想不出一种将我的持久性模型映射到域模型的干净方法。我希望我的Post 域模型界面看起来像这样:

public interface IPost
{
   int Id { get; }
   string Text { get; set; }
   public IEnumerable<ILike> Likes { get; }
   void Like();
}

现在下面的实现会是什么样子?也许是这样的:

public class Post : IPost
{
   private readonly PostPersistence _postPersistence;
   private readonly INotificationService _notificationService;

   public int Id 
   { 
       get { return _postPersistence.Id }
   }

   public string Text 
   { 
       get { return _postPersistence.Text; }
       set { _postPersistence.Text = value; }
   }

   public IEnumerable<ILike> Likes
   {
       //this seems really out of place
       return _postPersistence.Likes.Select(likePersistence => new Like(likePersistence ));
   }

   public Post(PostPersistence postPersistence, INotificationService notificationService)
   {
       _postPersistence = postPersistence;
       _notificationService = notificationService;
   }

   public void Like()
   {
       _postPersistence.Likes.Add(new LikePersistence());
       _notificationService.NotifyPostLiked(Id);
   }
}

我花了一些时间阅读有关 DDD 的内容,但大多数示例都是理论上的,或者在域层中使用了相同的 ORM 类。我的解决方案似乎很丑陋,因为实际上域模型只是 ORM 类的包装器,它似乎不是以域为中心的方法。 IEnumerable&lt;ILike&gt; Likes 的实现方式也让我感到困扰,因为它不会从 LINQ to SQL 中受益。还有哪些其他(具体的!)选项可以创建具有更透明持久性实现的域对象?

【问题讨论】:

  • 这是什么语言?用您正在使用的编程语言标记您的问题真的很有帮助。您应该将 C# 和 .Net 添加到问题的标签中。
  • 我已经添加了这些标签,但我的问题更独立于平台。任何用 OO 语言写的答案都会让我满意。
  • 不幸的是,EF 并不理想,因为如果您希望 EF 管理您的实体,您要么必须执行您所描述的操作,要么必须使用自动属性“公开”您的类并拥有默认的无参数构造函数

标签: c# .net architecture domain-driven-design persistence


【解决方案1】:

在 DDD 中持久化的目标之一是persistence ignorance,这在某种程度上似乎是您努力的目标。我在您的代码示例中看到的问题之一是您的实体实现了接口并引用了存储库和服务。在 DDD 中,实体不应该实现只是自身抽象并且对存储库或服务具有实例依赖关系的接口。如果实体上的特定行为需要服务,则将该服务直接传递给相应的方法。否则,与服务和存储库的所有交互都应在实体之外完成;通常在应用程序服务中。应用程序服务在存储库和服务之间进行协调,以便调用域实体上的行为。因此,实体不需要直接引用服务或存储库——它们所拥有的只是一些修改该状态并保持其完整性的状态和行为。然后,ORM 的工作就是将此状态映射到关系数据库中的表。 NHibernate 等 ORM 可以让你获得较大程度的persistence ignorance

更新

我仍然不想将带有 INotificationService 的方法公开为 参数,因为这个服务应该是内部的,上面的层不要 需要了解它。

Post 类的当前实现中,INotificationService 与该类具有相同或更高的可见性。如果INotificationService 是在基础设施层中实现的,它就必须具有足够的可见性。查看hexagonal architecture,了解现代架构中的分层概述。

附带说明,与通知相关的功能通常可以放入domain events 的处理程序中。这是实现高度解耦的强大技术。

如果使用单独的 DTO 和域类,您将如何解决 域对象不知道时的持久性同步问题 关于它的底层 DTO?如何跟踪更改?

DTO 和相应的域类存在的原因非常不同。 DTO 的目的是跨系统边界传送数据。 DTO 与域对象不是一一对应的——它们可以表示域对象的一部分或对域对象的更改。跟踪更改的一种方法是让 DTO 明确其包含的更改。例如,假设您有一个允许编辑Post 的 UI 屏幕。该屏幕可以捕获所做的所有更改,并将这些更改以命令 (DTO) 的形式发送到服务。该服务将加载适当的Post 实体并应用命令指定的更改。

【讨论】:

  • 这个链接真的很有帮助,它部分解决了我在回复 user1568656 的帖子中提到的问题。我仍然不想公开带有INotificationService 作为参数的方法,因为这个服务应该是内部的,上面的层不需要知道它。当域对象不知道其底层 DTO 时,如果使用单独的 DTO 和域类,您将如何解决持久性同步问题?如何跟踪变化?一些代码示例将非常有帮助。 (对不起,我不能投票,没有足够的声誉)
  • 我真的很喜欢领域事件的想法。一个实体不需要知道通知,它只是触发一个事件,所有感兴趣的服务/实体注册为监听器。我认为你误解了我对第二部分的理解:我所说的 DTO 是指我的持久性类。如何在域实体的状态发生更改后更新 DTO?实体不知道其底层 DTO(可能不止一个)。它是否也由某种事件维护?例如Post 实体暴露LikeAdded(Like like) 事件,作为响应,某些服务会创建相应的LikePersistence?
  • 使用像 EF 或 NHibernate 这样的 ORM,您不需要显式的持久性类。映射直接在域类和关系表之间进行。因此,当您加载域类并使会话保持打开状态时,对该域类所做的任何更改都将自动由框架持久化。
  • 是的,我明白了,但是我读过很少的资源像 mehdi-khalili.com/… 这样的资源,其中指出对于大型应用程序,建议使用单独的域和持久类,因为(尽管有所有优点)ORM 仍然限制类的开发在某些方面。我没有找到一些代码示例来说明如何做到这一点。
  • 在使用 NHibernate 时,我确实发现需要有一个中间 DTO 来映射到该中间 DTO 的情况,该中间 DTO 又映射到适当的域类。但是,这仅适用于component mappings,所需要的只是域类和 DTO 之间的一组方法映射。换句话说,不需要更改跟踪,因为为DDD value objects 进行了组件映射。
【解决方案2】:

我认为你需要做更多的研究,查看所有选项并决定是否真的值得为完整的 DDD 实施而烦恼,我最近几天自己去过那里,所以我会告诉你我的经验。

EF Code first 很有希望,但是有很多问题,我在这里有一个条目 Entity Framework and Domain Driven Design。使用 EF,您的域模型可以由 EF 持久化,而无需创建单独的“持久性”类。您可以使用 POCO(普通旧对象)并启动并运行一个简单的应用程序,但正如我对我所说,它还没有完全成熟。

如果您使用 LINQ to SQL,那么最常见的方法是将“数据传输对象”手动映射到业务对象。对于大型应用程序而言,手动执行此操作可能会很困难,因此请检查 Automapper 之类的工具。或者,您可以简单地将 DTO 包装在一个业务对象中,例如

  public class Post
   {
      PostPersistence Post { get; set;}
      public IList<LikePersistence> Likes { get; set; }
       .....
   }

NHibernate:不确定,好久没用了。

我对此的感觉(这只是一个观点,我可能是错的)是你总是不得不做出妥协,你不会在那里找到完美的解决方案。如果你再给 EF 几年,它可能会到达那里。我认为将 DTO 映射到 DDD 对象的方法可能是最灵活的,因此寻找自动映射工具可能值得您花时间。如果你想让它保持简单,我最喜欢的是在需要时围绕 DTO 的一些简单包装器。

【讨论】:

  • 我想说清楚:我需要 DTO 和域实体的单独类。 EF Code First 和 NHibernate 非常灵活,但据我所知,它们并不完全透明,并且在某些方面破坏了适当的封装。例如我的PostPersistence 暴露了IList&lt;LikePersistence&gt; Likes。我不想允许手动插入该列表。域之上的层应该调用Like() 方法,而不是可能包括一些额外的处理。
  • 您可以考虑使用分离实体的 EF 以及它应该如何在 N 层应用程序中工作,这可能会(几乎)为您提供所需的一切,但它会伴随您学习如何在 EF 中处理/附加对象和对象图并将它们持久化。我认为您可能需要考虑 2-3 年后的情况,您真的希望自己和您的同事通过冗长的映射代码或充满事件和通知的代码吗?
猜你喜欢
  • 2014-08-11
  • 1970-01-01
  • 2016-08-14
  • 1970-01-01
  • 2013-02-19
  • 1970-01-01
  • 2013-08-23
  • 1970-01-01
  • 2015-08-17
相关资源
最近更新 更多