存储库作为工厂?

时间:2015-01-08 13:54:13

标签: c# entity-framework design-patterns domain-driven-design ddd-repositories

今天,我需要设计一个实体,该实体持有对它的聚合根的引用。为了确保实体的实例引用与其包含的聚合根相同的聚合根,我做了一些限制,只有聚合根才能创建实体。

public class Aggregate {
   public int Id { get; }
   public IEnumerable<Entities> Entities { get; }
   public Entity CreateEntity(params);
}

public class Entity {
   public int Id { get; }
   public Aggregate Parent { get; }
}

突然间,一个关于聚合的一个非常重要的概念打击了我:聚合物不会神奇地出现。没有&#39; new Aggregate(id);&#39;在DDD世界。

所以,现在我问......谁负责创建它们?我知道有工厂等,但考虑到聚合的身份可能是数据库产生的代理,存储库负责聚合创建是否合理呢?

public class MyAggregate {
    public int Id { get; private set; }

    protected MyAggregate() {}

    public MyAggregate(int id) {
        Id = id;
    }
}

public interface IMyAggregateRepository {

    MyAggregate Create();
    void DeleteById(int id);
    void Update(MyAggregate aggregate);
    MyAggregate GetById(int id);
    // no Add() method on this layer!
}

private class EfMyAggregateRepository : IAggregateRepository {

    public EfMyAggregateRepository(DbContext context) {
        ...
    }

    public MyAggregate Create() {
        var pto = context.Create<MyAggregate>();
        context.Set<MyAggregate>().Attach(pto);
        return pto;
    }

}

这样,数据库(或例如EF)就可以自动生成密钥,也可以在存储库中定义验证规则,如果正在修改(和更新)等实体也适用。

或者我现在混在一起吗?这更像是服务/工厂的任务吗?

3 个答案:

答案 0 :(得分:7)

存储库只是抽象持久性,当它恢复(也许,存储本身可以恢复)聚合根时,它不会创建它。存储库的目的不是创建对象。

工厂的目的是创建对象,但是当创建不简单时使用工厂(如新的myobject())并且它取决于某些规则或者您不知道要求哪种具体类型(抽象工厂)。

关于聚合根必须来自某个地方,我不同意。他们不必须,但从语义的角度来看它是有意义的。我一直做“新的MyAggregate(id)”,这不是问题,没有必要根据一些武断的规则强迫事情,只因为有人这么说。如果你有充分的理由(设计,技术),那就去做吧。如果没有,请不要使你的生活复杂化。

答案 1 :(得分:0)

聚合和聚合根

在实际应用中,不需要显式Aggregate实现。 Aggregate是与entities绑定的相关aggregation relation的集合,其中一个主entity名为Aggregate Root

您只需确定entities aggregate roots是什么,然后使用aggregate root encapsulation and other rules设计您的类和聚合设备。

考虑到这一点,我建议遵循接口和类

interface IAggregateRoot<TKey> 
{
    TKey Key { get; }
}

//entity which is aggregate root
class Car : IAggregateRoot<int>
{
   int Key { get; }
   Ienumerable<Door> Doors { get; }
}

//other entity, not aggregate root
class Door 
{
   string Colour { get; set; }
}

//generic interface with IAggregateRoot constraint
interface IRepository<TAggregateRoot, TKey> where TAggregateRoot : IAggregateRoot
{
   TAggregateRoot Get(TKey key)
   //other methods
}

工厂和存储库

factoriesrepositories技术上都会创建aggregate roots的实例和其他有界聚合entities的实例。两者都构造aggregate但具有不同的语义。

<强>存储库

Repository从持久存储中恢复aggregate(考虑存储模型)并假设聚合处于一致状态,即不违反聚合不变量。

如果存在不变违规,则Repository必须执行某些操作,因为已创建的聚合不一致(?!)。

<强>工厂

Factory更像是建筑师。它构造aggregate,了解它的不变量。在aggregate构造期间aggregate可能处于不一致状态,但随着aggregate构造过程的完成,它必须尊重不变量并保护它们。

如果由于某些原因导致不变违规factory无法构建aggregate并将其删除。

有时存储库使用工厂来创建实例,但在大多数情况下,我认为最好将它们完全分开。

实施例

工厂如何运作的一个例子。

假设Car类的不变量是'Car只能有2门或4门'。

public interface ICar : IAggregateRoot<int>
{
    int Key { get; }
    IEnumerable<IDoor> Doors { get; }
    SetDoors(IEnumerable<IDoor> doors);
}

public class Car : ICar
{
    //take a look here, it's internal
    internal IList<IDoor> Doors { get; set;}

    public int Key { get; set; }
    public IEnumerable<IDoor> Doors { get { return Doors; } }

    //aggregate root always controls aggregate invariants
    public SetDoors(IEnumerable<IDoor> doors)
    {
        if (doors.Count() != 2 || doors.Count() != 4)
            throw new ApplicationException();
        Doors = doors.ToList();
    }

}

interface IDoor { }

class Door : IDoor { }

//generic interface with IAggregateRoot constraint
interface ICarFactory
{
    ICar CreateCar(int doorsCount)
}

public class CarFactory : ICarFactory
{
    public ICar CreateCar(int doorsCount) 
    {
        if (doorsCount != 2 && doorsCount != 4)
            throw new ApplicationException();

        var car = new Car();
        car.Key = 1;

        car.Doors = new List<IDoor>();
        car.Doors.Add(new Door());
        //now car is inconsistent as it holds just one door
        car.Doors.Add(new Door());
        //now car is consistent
        return car;
     }
 } 

这是一个人为的例子,但有时候聚合不变量太复杂了,无法在构造过程中始终满足它们。

现在假设Factory和Aggregate Root具有密切连接,因为Factory知道Car内部字段。

最后,我认为你误解了一些概念并建议你阅读Evans DDD书。

编辑:可能我错过了MyAggregate实际上是MyAggregateRoot的事实。但对我来说,混合这些概念很奇怪。此外,在SO上有一个问题,显式人工聚合类包含所有聚合内部只是为了显示聚合边界。我更喜欢可接受的AggregateRoot概念,就像我见过http://ptgmedia.pearsoncmg.com/images/chap10_9780321834577/elementLinks/10fig05.jpg

的大多数图表一样

答案 2 :(得分:0)

  

&#34;突然间,关于聚合的一个非常重要的概念打击了我:   聚合物不会神奇地出现在其中。&#34;

您必须明白,通常不会直接将new聚合直接添加到更密切关注泛在语言(UL)

例如,业务专家可能会说&#34;网站访问者应该能够注册成为客户&#34; 而不是new Customer(visitorId, ...)您可能会有类似Customer customer = visitor.register(...)的内容{1}}。

使用纯技术工厂并不能帮助解决这个问题,因为这些工作不是UL的一部分,可以实现一个非常不同的目标,例如从客户的角度简化创建过程或者解耦客户端来自具体实施。

  

&#34;没有&#39; new Aggregate(id);&#39;在DDD世界。&#34;

实际上根本不是真的。如果没有合法的域概念可以创建聚合(即使有),直接new聚合就可以了。

仅仅为了避免new而引入一个不需要的技术工厂只会使设计复杂化。

  

&#34;考虑到聚合的身份可能是代理人   由数据库生成,这不可能是合理的   存储库负责聚合创建?&#34;

存储库的工作是抽象出持久性细节以及为聚合检索定义明确的合同,而不是创建聚合。显然,由于聚合必须从查询返回,因此它们的实例化必须发生在链中的某个地方,但它很可能被委托给其他组件,例如工厂,ORM等。

如果您需要数据库生成的标识,则可以在存储库中放置一个返回此类标识的操作。例如,您可能在存储库上有一个public int nextIdentity()方法,它返回数据库序列的下一个值。

遵循接口隔离原则(ISP),您可以决定为该任务创建专用接口,例如CustomerIdentityGenerator