存储库,工厂和分层结构化数据

时间:2013-12-06 12:33:15

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

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

如果数据本质上是分层的,就像某种树结构那样。例如:

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;
    }
}

如果我尝试在工厂建立孩子,那么我需要再次访问回购。如果我在Repo中这样做,我觉得我正在做的工作应该是工厂。不知道如何解决这个问题。

我有一个想法是,Foo不是聚合根,而是FooTree是聚合根。所以试图得到任何Foo我需要创建整个树,这意味着我可以将Foo对象的集合传递给FooTreeFactory。

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:3)

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

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

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

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

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

更新

重新阅读你的问题之后,我认为你和@enrico都是正确的:

  

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

答案 2 :(得分:2)

Do not reference other AggregateRoot when designing Aggregate.

因此,在常见情况下,A Foo不会将其他Foos引用为树或父。如果确实需要,这可能是查询要求。例如,显示数据。 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。