存储库作为工厂

本文关键字:工厂 存储 | 更新日期: 2023-09-27 18:36:41

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

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

突然间,一个关于聚合的非常重要的概念突然出现在我身上:聚合不会神奇地突然出现。在DDD世界中没有"new Aggregate(id)"这样的东西。

所以,现在我要问..谁负责创建它们?我知道有工厂之类的,但考虑到聚合的身份可能是数据库生成的代理项,存储库负责聚合创建不是很合理吗?

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)就可以自动生成密钥,也许存储库中的定义验证规则也适用于正在修改(和更新)实体等。

还是我现在把事情搞混了?这更像是服务/工厂的任务吗?

存储库作为工厂

存储库

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

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

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

聚合

和聚合根

在实际应用中,不需要显式Aggregate实现。 Aggregate 是相关entities的集合,以aggregation relation为界,给定一个称为 Aggregate Root 的主entity

您只需要决定aggregate roots哪些entities,然后使用 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;
     }
 } 

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

现在假设工厂和聚合根具有密切的联系,因为工厂知道汽车内部字段。

最后,我想你误解了一些概念,建议你读一读埃文斯DDD的书。

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

"突然间,一个关于聚合的非常重要的概念突然出现在我身上: 聚合不会神奇地凭空出现。

您必须了解,通常最好不要直接new聚合,只是为了更紧密地遵循无处不在的语言 (UL)。

例如,业务专家可能会说"网站访问者应该能够注册成为客户",因此您可能new Customer(visitorId, ...)而不是Customer customer = visitor.register(...)

使用纯技术工厂在这方面无济于事,因为这些工厂不是UL的一部分,并且将服务于一个非常不同的目标,例如从客户端角度简化创建过程或将客户端与特定实现分离。

"在DDD世界中没有'新聚合(id)'这样的东西。

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

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

"考虑聚合体的身份可能是替代品 由数据库生成,难道不是合理的 存储库负责聚合创建?

存储库的工作是抽象出持久性细节,并为聚合检索定义显式协定,而不是创建聚合。显然,由于聚合必须从查询返回,因此它们的实例化必须发生在链中的某个位置,但它很可能委托给 antoher 组件,例如工厂、ORM 等。

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

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