【问题标题】:Entities depends upon Repositories abstractions实体依赖于存储库抽象
【发布时间】:2014-10-26 03:28:24
【问题描述】:

如何让实体懒加载它的关系?

例如:帖子和评论模型,其中帖子可以有 0 条或更多评论。如何使 Post 实体上的 getComments() 方法延迟加载其评论?

我的第一个想法是在我的 Post 实体中注入一个 CommentRepository,这有多糟糕?既然 Entity 和 Repositories 是 may 域的一部分,那么为什么它们不能对彼此有双向的了解呢?

谢谢

更新
我知道有许多优秀的行业标准 ORM 可以为主要语言执行延迟加载,但我不想依赖它的魔力。我正在寻找一种与 ORM/DBAL 无关的解决方案,以确保应用程序的低耦合性。

【问题讨论】:

  • 您对数据访问层使用什么解决方案。您的存储库的实现是什么?

标签: oop design-patterns domain-driven-design object-oriented-analysis


【解决方案1】:

聚合表示一致性边界,因此永远不需要延迟加载相关数据,因为聚合作为一个整体应该始终保持一致。属于聚合的所有对象都不需要单独存在。如果您确实有一个拥有自己生命周期的对象,则需要将其从聚合中移除。

当您确实发现需要这样做时,您可能需要重新考虑您的设计。可能是您正在使用对象模型进行查询。您应该拥有一个可以执行此功能的轻量级查询模型。

将存储库或服务注入实体通常不是最好的主意。应该首选双分派机制。

但在你的情况下,我仍然会尝试延迟加载。

【讨论】:

  • 同意;另外Post 不应该包含对Comment 的对象引用,只有 ID。参见,例如,stackoverflow.com/questions/4919687/… 中的最高投票答案
  • 所以我的 Post 实体上不应该有 getComments() 方法?
  • 好吧,据我所知,我不会在 Post 上使用 getComments() 方法。如果你确实有一个,那么宁愿使用双重调度,这样你就不会注入存储库:Post.getComments(ICommentRepository)。您需要问的问题是为什么您需要 Post 上的 cmets。如果仅用于查询,则发送回最基本结构的单独ICommentQuery 实现可能就足够了。尽管一旦有了存储库,它就意味着一个实体。如果Comment 不是属于另一个实体的实体,则无论如何都需要将所有 cmets 加载到聚合中。
【解决方案2】:

考虑使用子类 Post 的代理,覆盖 getComments() 方法。使用 CommentRepository 注入代理并在覆盖的 getComment() 方法中访问它。

这就是 ORM 通常的做法。它使您的域类保持干净,因为只有代理依赖于数据访问机制。

【讨论】:

  • 代理和实体之间有什么区别?对于每个实体,我都会有一个代理,那么为什么要同时维护呢?
  • 因为您可以交换代理及其数据访问技术,而不会影响实体。恕我直言,一个设计良好的实体/域模型必须不知道这些问题。此外,您可以更改代理用于执行延迟加载的策略,并且本质上是最合适的插件。同样,所有这些都不会影响核心模型。符合 SRP 和 OCP。整洁。
【解决方案3】:

首先,您应该将领域概念与实现细节分开。聚合模式是关于如何组织你的域,而延迟加载是一个实现细节。

另外,我不同意@Eben Roux 关于协议不一致的观点。在我看来,延迟加载并不矛盾。我说明原因。

延迟加载自身

要了解如何实现延迟加载,您可以参考 Martin Fowler 的 PoEAAA 模式 'Lazy loading'。对我来说,代理模式是最好的解决方案。 此外,重要的是现在大多数 ORM 支持延迟加载,但对于数据模型(而不是域模型)。

将数据模型和域模型分开并使用存储库来隐藏这种转换是一种很好的做法:

分离的域和数据模型

在这种情况下,域模型的对象是在隐藏 ORM 上下文的存储库中构建的。需要的数据对象和所有的关联由ORM加载,然后转换为领域模型,最后返回构造好的领域对象。

问题是如何在域对象的创建过程中而不是在它的生命周期中加载一些关联。您可以在实体内部使用 Repoisotry,我认为它没有任何问题。它看起来像:

public class Post {

    private ICommentsRepository _commentsRepository;

    private IList<Comments> _comments;

    //necessary to perform lazy loading (repository always wroks with ids)
    private IList<int> _commentIds;

    //realize lazy loading
    ...
}

有问题:

  1. 您的模型现在变得不清楚。它包含“技术”信息,例如_commentIds
  2. 只要您想定义ICommentsRepository,您就可以将Comment 声明为聚合根。如果我们将聚合模式引入域模型,则应该为聚合根创建存储库。因此这意味着CommentPost 是不同的聚合根。并且可能不是您想要的。

有更好的解决方案:

public interface ICommentList {
...
}

public class CommentList : ICommentList {
...
}

public class CommentListProxy : ICommentList {

   private CommentList _realCommentList;

   private IList<int> _commentIds;

   //realize lazy loading here using ORMs capabilities! 
   //don't use repository here!

}

public class Post {

   private ICommentList _commentList;

   ...
}

后存储库将使用代理对象初始化_commentList 字段。另外,有必要说:

  1. CommentListProxy 与数据模型层有关,与域模型无关。它使用 ORM 功能来实现延迟加载
  2. 因此不使用存储库,因此您可以将CommentList 视为Post 聚合的一部分。

这种方法唯一可能的缺点是在使用域对象操作时会进行隐式数据库查询Post 类的用户必须清楚这一点。

智能 ORM

最后有一种 ORM 允许您对域和数据使用相同的模型。它以与数据模型相同的方式实现域模型的延迟加载。看看 DataObjects.Net。在某些情况下,这是一个很好的解决方案。

【讨论】:

  • 为什么我需要有commentIds? Post 实体上的 getComments() 方法只会将调用委托给注入的 CommentRepository,我不明白为什么我需要存储相关 cmets 的 de ID...
  • 好的,您介绍了与 ORM 无关的解决方案,那么您希望将哪些参数传递给存储库以获取 cmets?如果您在域实例中保存数据实例 - 这是一种不好的做法。另外,我刚刚向您解释了为什么为非聚合根实体(我认为是 Comment)创建存储库是不好的:“只要您想定义 ICommentsRepository,您就声称 Comment 是聚合根。如果我们引入聚合模式在域模型中,应该只为聚合根创建存储库。因此这意味着评论和帖子是不同的聚合根..."
  • 我认为第二种解决方案(使用 CommentListProxy)是最好的一种。如果需要,您可以在 CommentListProxy 中保存 Post 的数据 (!) 实例,而不是 Comments id。这是可能的,因为 CommentListProxy 与数据层(!)相关,而不是域。因此,它可以操作 ORM 功能来延迟加载所有 cmets。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-19
相关资源
最近更新 更多