今天,我需要设计一个实体,该实体持有对它的聚合根的引用。为了确保实体的实例引用与其包含的聚合根相同的聚合根,我做了一些限制,只有聚合根才能创建实体。
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)就可以自动生成密钥,也可以在存储库中定义验证规则,如果正在修改(和更新)等实体也适用。
或者我现在混在一起吗?这更像是服务/工厂的任务吗?
答案 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
}
factories
和repositories
技术上都会创建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
。