【问题标题】:Repositories, factories and hierarchically structured data存储库、工厂和分层结构化数据
【发布时间】:2013-12-06 12:33:15
【问题描述】:

我正在阅读 Eric Evans 的域驱动设计,他概述了存储库和工厂之间的相互作用。存储库本身将调用数据库接口来获取结果集。然后将该结果集传递给一个工厂,该工厂将理解该结果集以重构对象。

如果数据本质上是分层的,比如某种树状结构,那会怎样。例如:

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Foo Parent { get; set; }
    public ICollection<Foo> { get; set; }

    // Other business like methods here

}

使用 DDD,我将拥有我的接口和实现:

public interface IFooRepository
{
    Foo Get(int id);
}

public interface IFooFactory<TSource>
{
    Foo Create(TSource source);
}

public class SqlFooRepository: IFooRepository
{
    private readonly IFooDao dao;
    private readonly IFooFactory<SqlDataReader> factory;

    public SqlFooRepository(IFooDao dao, IFooFactory factory)
    {
        this.dao = dao;
        this.factory = factory;
    }

    public Foo Get(int id)
    {
        var resultSet = dao.Find(id);
        return factory.Create(resultSet);
    }
}

public class SqlFooFactory: IFooFactory<SqlDataReader>
{
    public Foo Get(SqlDataReader reader)
    {
        var foo = new Foo();
        foo.Id = (int) reader["id];
        foo.Name = (string) reader["name"];
            // Do I build the children accounts here
        return foo;
    }
}

如果我尝试在工厂中构建孩子,那么我需要再次访问该仓库。如果我在回购中做这件事,我觉得我在做应该为工厂做的工作。不知道如何解决这个问题。

我的一个想法是 Foo 不是聚合根,而是 FooTree 是聚合根。因此,尝试获取任何 Foo 我需要创建整个树,这意味着我可以将 Foo 对象的集合传递给 FooTreeFactory。

任何帮助将不胜感激。

【问题讨论】:

  • 获取方法,您可以构建 Foo 和所有相关的子项;您应在 SQL 查询中从数据库加载所有需要的记录,并以通用方式构建您的树(您拥有执行此操作所需的所有数据,例如ParentId、ChildId 和构建你的树);您不必在这里与您的存储库或除后端之外的任何其他层交谈!
  • 您能否详细说明并以答案的形式进行。如果看起来最合适,我可以将您标记为已接受的答案。

标签: c# domain-driven-design ddd-repositories


【解决方案1】:

存储库处理聚合根,而不是实体。 因此,我建议您使用FooTree AR 解决方案并从数据库中检索它。 工厂不应依赖于存储库,因此您必须获取所有树数据并将它们传递给工厂。 或者,您可以在 AR 客户端(或 AR 本身)请求它们时实现 ORM 的延迟加载和“延迟加载”子级。

【讨论】:

    【解决方案2】:

    假设您在使用Foo 工作时需要所有孩子,您应该在存储库中获取它们并在您的工厂中重建层次结构。如果您不一定需要它们;或者您可能在某些极端情况下需要它们,您可以考虑懒惰地获取它们,但我认为这不是您想要的。

    在构建这样的层次结构时,您希望确保只访问数据库一次。例如,如果您在一次调用数据库中获取Foo foo1,然后使用相同的存储库方法repo.Get(foo1.Id) 获取foo1 的子代,那么每个子代都会有一个额外的数据库往返......如果你也为每个孩子递归地这样做,那么还有更多。您不希望这样,因为它会导致未知数量的额外数据库往返(select N+1 problem 的变体)。

    您想要的是一个可以在一次数据库往返中获取完整层次结构的存储库。如果您使用的是 ORM,那么 ORM 通常会内置一些东西来为您处理这个问题;例如NHibernate has a DistinctRootEntityResultTransformer 就是这样做的。

    如果您想使用纯 sql 存储库进行此操作,那么我将创建一个存储过程(假设您使用的是 Sql Server)从数据库中递归地获取层次结构中的所有 Foo 行并在 一个结果集到存储库。然后存储库将此结果集传递给您的工厂以创建对象树。

    因此,关键是不要将单个 Foo 传递给您的工厂,而是将 reader 传递给工厂以读取 Foo 行的结果集,而不是单行。

    更新

    在重新阅读您的问题后,我认为您和@enrico 是正确的:

    [...] FooTree 是聚合根。所以试图得到任何Foo 我会 需要创建整个树,这意味着我可以传递一个集合 的 Foo 对象到 FooTreeFactory

    【讨论】:

      【解决方案3】:

      Do not reference other AggregateRoot when designing Aggregate.

      所以在一般情况下,A Foo 不会将其他 Foo 引用为树或父级。如果您确实需要,这可能是一个查询要求。例如,显示数据。 DDD 不擅长查询,因此在没有那么多 DDD 限制和规则的情况下,使用贫血模型更容易实现这一点。

      更新
      如果此层次结构用于域派生,您可以考虑Specification pattern

      public class FooBarSpecification
      {
          public Foo Parent //injected by constructor
          public ICollection<Foo> //injected by constructor
      
          public boolean isSatisfiedBy(Foo foo) {
              //use FooTree here to
          }
      
      }
      

      客户端可以使用 FooRepository 来获取 Foos 来启动规范。

      另一个解决方案是使用 DomainService。

      【讨论】:

      • 但是我在 Foo 对象上有一些方法,它们会调用 Foo 父对象上的其他方法来完成它的工作。这就是为什么树是必要的。更新:在模型中,这种关系存在的事实非常重要。那我该如何模拟这种关系呢?
      • @uriDium 抱歉,忘记域派生案例。答案已更新。
      猜你喜欢
      • 2020-03-14
      • 2015-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-20
      • 1970-01-01
      相关资源
      最近更新 更多