Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to split doctrine entity into more domain entities in different bounded contexts?

I am trying my first steps in DDD (Domain-driven design) field. I love the wise rule, that you should split your entities into many smaller, context-specific entities (for example User entity tends to overgrow almost in every non-ddd or poorly designed application). But what are common options on how to do it (effectively) in php using Doctrine?

Let's say I have two Bounded contexts: Store and Invoicing

Each of them has two domain entities: for Store BC it's FirstTimeCustomer and RecurrentCustomer for Invoicing BC it's VatCustomer and NonVatCustomer

Now, in my infrastructure layer I want them all be saved in same (at least base) table to be able to reference them by general UserId (Uuid). The problem for me is, how to do that, so I can use advantage of doctrine automapping in my repository implementations.

I read about Single Table Inheritance or Class Table Inheritance that may be solution, but:

I need to be able to work with User of same Uuid as it was different type in each context.

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

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

But it seems that I am forced here to chose what BC I want specific identity of entity bound to. So it does not make sense in context of DDD to me at all.

I also read about Mapped Superclasses, that looks like option too, but:

This means that One-To-Many associations are not possible on a mapped superclass at all.

And I need Customer to have relation to orders, that should be available in both entities (an also possibly not want it to be available is some new type of User entity in new BC).

I came to conclusion that I should be suspicious that I am missing something, is that? How should I achieve breaking god entity into smaller context specific ones?

like image 227
Tom Avatar asked Feb 14 '17 03:02

Tom


People also ask

What is the difference between domain and Bounded context?

The domain represents the problem to solve; the domain model is the model that implements the solution to the problem. Likewise, a subdomain is a segment of the domain, and a bounded context is a segment of the solution.

Can a bounded context have multiple aggregates?

Aggregates. The stategic DDD's bounded contexts typically contain multiple aggregates. Within the aggregates, you can model your system with the tactic DDD patterns, such as Entity, Value Object, Domain Event, Service and Repository.

What is a Bounded context in DDD?

Bounded Context and Ubiquitous Language. To solve the addressed issue, DDD provides the concept of Bounded Context. A Bounded Context is a logical boundary of a domain where particular terms and rules apply consistently. Inside this boundary, all terms, definitions, and concepts form the Ubiquitous Language.

What is domain context?

Domain Context module is an integration module for the Domain Access module and the Context module. The Domain Access module allows administrators to set specific settings for each of their domains that are using the same code base.


1 Answers

I will start with some general rules. When thinking DDD you must reject any thoughts about persistence (tables and primary keys and the like). You must design your aggregate roots and entities as if you will store them in a file. When you design the models, if a thought about tables or ORM comes to your mind you fail to do DDD; take a step back and restart.

As I see, you started OK by splitting your models by the bounded contexts. If you do not split them by BCs you end up with gigantic models that try to encompass all the behavior and then the persistence will affect the performance, because, in DDD, the aggregates are persisted in one single transaction. One other thing, try to avoid inheritance and use composition.

That being said, lets talk about your business. In the Authentication BC, there are Users that can authenticate, with some credentials. In the Store BC there are Customers that buy things. In the Invoice BC, there are also Customers but a different model (a different class if you want). In the Shipping BC there are also Customers but again, a different model. These Customer models share some properties along the BCs, like name and id. So, you use the id to identify a person from the real life but use different models to encapsulate their behavior depending on the context.

I think that you should not have a AbstractStoreCustomer, you should have only Customer and try to isolate what differentiate them in abstract class/interface, like BuyingHistoryProfile with 2 implementation FirstTimeCustomer and RecurrentCustomer. This class should contain only behavior regarding buying profile and it should be referenced by everyCustomer in the Store BC. Similarily, try to extract a class in the Invoicing BC for the price calculation.

Now that we've created the models, we can think about persistence. For every bounded context create a Customer table, that contains the shared properties (name and id) and the specific properties (buyingHistoryProfile for the Store BC, priceCalculationStrategy for the Invoicing BC etc).

If you wonder that there is a degree of data duplication then you are right, but it is normal, it is necessary to decouple the models. This duplicate data is synchronized when the User in the Authentication BC change its name: you update all the other models right then. From this point of view, Invoice and Store BC are down-streams, they use data from the Authentication BC; it depends on your business when the update is initiated.

Being a fan of Event-driven architectures, I recommend you to take a look at CQRS (and even Event Sourcing). I think that these architectures fit well in this application. CQRS could make your models 10 times cleaner, I tell you from my experience. With Event Sourcing it is very probable that you won't even have to use an ORM. You could use an ORM on the read side, if you really like them or you can't leave them. I personally don't like (and don't use) ORMs.

like image 132
Constantin Galbenu Avatar answered Oct 16 '22 22:10

Constantin Galbenu