存储库作为工厂
本文关键字:工厂 存储 | 更新日期: 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
}
工厂和存储库
从技术上讲,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;
}
}
这是人为的例子,但有时聚合不变量太复杂,无法在构造过程中始终满足它们。
现在假设工厂和聚合根具有密切的联系,因为工厂知道汽车内部字段。
最后,我想你误解了一些概念,建议你读一读埃文斯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
。