Tom*_*Tom 5 php domain-driven-design doctrine-orm
我正在尝试DDD(域驱动设计)领域的第一步.我喜欢明智的规则,你应该将你的实体分成许多较小的,特定于上下文的实体(例如,User实体往往在每个非ddd或设计不佳的应用程序中都会过度生长).但是在使用Doctrine的php中如何(有效地)实现它的常见选项是什么?
假设我有两个有界的上下文:Store和Invoicing
它们中的每一个都有两个域实体:对于Store BC而言FirstTimeCustomer,RecurrentCustomer
对于Invoicing BC它是VatCustomer和NonVatCustomer
现在,在我的基础设施层中,我希望它们全部保存在同一个(至少是基础)表中,以便能够通用UserId(Uuid)引用它们.对我来说问题是,如何做到这一点,所以我可以利用我的存储库实现中的doctrine自动化.
我读到了可能是解决方案的单表继承或类表继承,但是:
我需要能够使用User相同的Uuid,因为它在每个上下文中都是不同的类型.
我需要能够在商店BC中返回一些AbstractStoreCustomeraka FirstTimeCustomer或RecurrentCustomeron $storeCustomerRepository->find(1);.
并在发票BC中返回一些AbstractInvoicingCustomeraka VatCustomer或NonVatCustomeron $invoicingCustomerRepository->find(1);.
但似乎我被迫在这里选择BC我想要的实体的特定身份.所以在DDD的背景下对我来说根本没有意义.
我还阅读了Mapped Superclasses,它看起来也像是选项,但是:
这意味着根本不可能在映射的超类上进行一对多关联.
而且我需要Customer与订单有关系,这两个实体应该是可用的(也可能不希望它可用于User新BC中的一些新类型的实体).
我得出结论,我应该怀疑我错过了什么,是吗?我应该如何将上帝实体打造成更小的特定背景?
我将从一些一般规则开始.在考虑DDD时,您必须拒绝任何关于持久性的想法(表和主键等).您必须设计聚合根和实体,就像将它们存储在文件中一样.当你设计模型时,如果想到关于表或ORM的想法你就不能做DDD; 退一步重新开始.
正如我所看到的,你通过有限的上下文分割你的模型开始了.如果你没有通过BC拆分它们,你最终会想到包含所有行为的巨大模型,然后持久性会影响性能,因为在DDD中,聚合会持久存储在一个事务中.另一件事,尽量避免继承和使用组合.
话虽这么说,让我们谈谈你的业务.在Authentication卑诗省,有一些Users可以通过一些凭证进行身份验证.在Store卑诗省有Customers买东西.在InvoiceBC,也有Customers一个不同的模型(如果你想要一个不同的类).在ShippingBC省也有Customers一个不同的模型.这些Customer模型在BC上共享一些属性,例如name和id.因此,您使用它id来识别现实生活中的人,但使用不同的模型根据上下文封装他们的行为.
我认为你不应该有一个AbstractStoreCustomer,你应该只有Customer并尝试隔离它们在抽象类/接口中区分它们,就像BuyingHistoryProfile2实现FirstTimeCustomer和RecurrentCustomer.这个类应该只包含关于行为购买的个人资料,应该由每一个被引用Customer的StoreBC.类似地,尝试在InvoicingBC中提取一个类来计算价格.
现在我们已经创建了模型,我们可以考虑持久性.对于每个有界上下文,创建一个Customer表,其中包含共享属性(name和 id)和特定属性(buyingHistoryProfile对于StoreBC,priceCalculationStrategy对于InvoicingBC等).
如果您想知道存在一定程度的数据重复,那么您是对的,但这是正常的,有必要将模型分离.当BC中的名称更改其名称时User,将同步此重复数据Authentication:然后更新所有其他模型.从这个角度来看,Invoice和StoreBC是向下的料流,它们使用来自数据AuthenticationBC; 这取决于您的业务何时启动更新.
作为事件驱动架构的粉丝,我建议您看看CQRS(甚至是事件采购).我认为这些架构非常适合这个应用程序.根据我的经验,CQRS可以让你的模型清洁10倍.通过事件采购,您很可能甚至不必使用ORM.你可以ORM在阅读方面使用,如果你真的喜欢它们,或者你不能离开它们.我个人不喜欢(并且不使用)ORMs.