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