How to split a doctrine object into other domain objects in different limited contexts?

I am trying to take the first steps in DDD ( Domain-driven design ). I like the wise rule that you should split your entities into several smaller, context-specific objects (for example, a User object tends to grow into almost every non-ddd or poorly designed application). But what are the common options for how to do this (efficiently) in php using Doctrine?

Say I have two Bounded contexts: Store and Invoicing

Each of them has two domains: for BC it FirstTimeCustomer and RecurrentCustomer for BC billing, it is VatCustomer and NonVatCustomer

Now, in my infrastructure layer, I want all of them to be stored in one (at least the base) table so that they can be referenced by a common UserId (Uuid). The problem for me is how to do this, so I can take advantage of doctrine auto-tuning in my repository implementations.

I read about single table inheritance or Table class inheritance , which might be a solution, but:

I need to be able to work with User the same Uuid, since in each context it was of a different type.

I need to return some AbstractStoreCustomer aka FirstTimeCustomer or RecurrentCustomer in $storeCustomerRepository->find(1); in Store BC.

And return AbstractInvoicingCustomer aka VatCustomer or NonVatCustomer to $invoicingCustomerRepository->find(1); at Invoicing BC.

But it seems to me that I am forced to choose here why I want to attach a specific personality to the entity. So this does not make sense in the context of DDD for me at all.

I also read about Mapped Superclasses , which also looks like an option, but:

This means that one-to-many associations are not possible when comparing a superclass in general.

And I need Customer deal with orders that should be available in both objects (maybe they also don’t want it to be available - this is a new type of User object in the new BC).

I came to the conclusion that I should be suspicious that I missed something, right? How should I achieve the breaking up of a divine being into smaller specific contexts?

+5
source share
1 answer

I will start with some general rules. When thinking DDD, you should reject any thoughts about saving (tables and primary keys, etc.). You must create your aggregate roots and entities as if you saved them in a file. When you develop models, if you think about tables or ORMs, you will not be able to do DDD; take a step back and restart.

As I can see, you started OK by dividing your models into a limited context. If you do not divide them into BC, you will get giant models that try to cover all the behavior, and then persistence will affect performance, because in DDD aggregates are stored in one transaction. One more thing, try to avoid inheritance and use of composition.

Speaking, let's talk about your business. In Authentication BC, there is a User that can authenticate with some credentials. Store BC has Customers who buy things. Invoice BC also has Customers , but a different model (if you need a different class). Shipping BC also has Customers , but again, a different model. These Customer models share some properties along BC, such as name and id . Thus, you use id to identify a person from real life, but use different models to encapsulate your behavior depending on the context.

I think that you should not have AbstractStoreCustomer , you should have only Customer and try to highlight what distinguishes them in the abstract class / interface, for example BuyingHistoryProfile with 2 implementations of FirstTimeCustomer and RecurrentCustomer . This class should only contain behavior regarding the purchase profile, and should be referenced by each Customer in Store BC. Similarly, try extracting a class in Invoicing BC to calculate the price.

Now that we have created the models, we can think of perseverance. For each limited context, create a Customer table that contains common properties ( name and id ) and specific properties ( BuyingHistoryProfile for Store BC, priceCalculationStrategy for Invoicing BC, etc.).

If you are wondering if there is a degree of data duplication, then you are right, but this is normal, you need to separate the models. This duplicate data is synchronized when User in Authentication BC changes its name: you update all other models at once. From this point of view, Invoice and Store BC are downstream; they use data from Authentication BC; it depends on your business when you upgrade.

As a fan of event-driven architecture, I recommend you take a look at CQRS (and even Event Sourcing). I think these architectures fit well in this application. CQRS can make your models 10 times cleaner, I will tell you from my experience. With Event Sourcing, it is very likely that you do not even have to use ORM . You can use the ORM on the read side if you really like them, or you cannot leave them. I personally do not like (and do not use) ORM s.

+5
source

Source: https://habr.com/ru/post/1264190/


All Articles