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?
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.
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.
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.
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.
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) ORM
s.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With