I'd like to start this article with a quote from Ross Tuck's article "Persisting Value Objects in Doctrine". He describes different ways of persisting value objects when using Doctrine ORM. At the end of the page he gives us the following option - the "nuclear" one:
[...] Doctrine is great for the vast majority of applications but if you’ve got edge cases that are making your entity code messy, don’t be afraid to toss Doctrine out. Setup an interface for your repositories and create an alternate implementation where you do the querying or mapping by hand. It might be a PITA but it might also be less frustration in the long run.
As I discovered recently, you don't need an edge case to drop Doctrine ORM altogether. But since there are lots of projects using Doctrine ORM, with developers working on them who would like to apply DDD patterns to it, I realized there is probably an audience for a few practical suggestions on storing aggregates (entities and value objects) with Doctrine ORM.
Designing without the ORM in mind
When you (re)learn how to design domain objects using Domain-Driven Design patterns, you first need to get rid of the idea that the objects you're designing are ever going to be persisted. It's important to stay real about your domain model though; its state definitely needs to be persisted some day, or else the application won't meet its acceptance criteria. But while designing, you should not let the fact that you're using a relational database get in the way. Design the objects in such a way that they are useful, that you can do meaningful things with them, and that they are trustworthy; you should never encounter incomplete or inconsistent domain objects.
Still, at some point you're going to have to consider how to store the state of your domain objects (after all, your application at one point is going to shut down and when it comes up, it needs to have access to the same data as before it was restarted). I find that, when designing aggregates, it would be best to act as if they are going to be stored in a document database. The aggregate and all of its parts wouldn't need to be distributed across several tables in a relational database; the aggregate could just be persisted as one whole thing, filed under the ID of the aggregate's root entity.
More common however is the choice for a relational database, and in most projects such a database comes with an ORM. So then, after you've carefully designed your aggregate "the right way", the question is: how do we store this thing in our tables? A common solution is to dissect the aggregate along the lines of its root entity and optionally its child entities. Consider an example from a recent workshop: we have a purchase order and this order has a number of lines. The PurchaseOrder
is the root entity of the aggregate with the same name. The Line
objects are the child entities (i.e. they have an identity - a line number - which is only unique within the aggregate itself). PurchaseOrder
and Line
all have value objects describing parts or aspects of these entities (i.e. the product ID that was ordered, the quantity that was ordered, the supplier from whom it was ordered, and so on). This would be a simplified version of PurchaseOrder
and Line
:
<?php
final class PurchaseOrder
{
/**
* @var PurchaseOrderId
*/
private $id;
/**
* @var SupplierId
*/
private $supplierId;
/**
* @var Line[]
*/
private $lines = [];
private function __construct(
PurchaseOrderId $purchaseOrderId,
SupplierId $supplierId
) {
$this->id = $purchaseOrderId;
$this->supplierId = $supplierId;
}
public static function create(
PurchaseOrderId $purchaseOrderId,
SupplierId $supplierId
): PurchaseOrder
{
return new self($purchaseOrderId, $supplierId);
}
public function addLine(
ProductId $productId,
OrderedQuantity $quantity
): void
{
$lineNumber = count($this->lines) + 1;
$this->lines[] = new Line($lineNumber, $productId, $quantity);
}
public function purchaseOrderId(): PurchaseOrderId
{
return $this->id;
}
// ...
}
final class Line
{
/**
* @var int
*/
private $lineNumber;
/**
* @var ProductId
*/
private $productId;
/**
* @var OrderedQuantity
*/
private $quantity;
public function __construct(
int $lineNumber,
ProductId $productId,
OrderedQuantity $quantity
) {
$this->lineNumber = $lineNumber;
$this->productId = $productId;
$this->quantity = $quantity;
}
// ...
}
To turn the PurchaseOrder
into something manageable by Doctrine, we need to do several things:
- Mark the entity class as a Doctrine entity.
- Map the class attributes to database columns, providing the correct types for them.
- Use Doctrine
Collection
s instead of arrays for one-to-many relations.
We can accomplish step 1. by using just one annotation:
/**
* @ORM\Entity()
*/
final class PurchaseOrder
{
But when mapping the entity's attributes to database columns, we already get into trouble because, for instance, the $id
attribute contains a value of type PurchaseOrderId
. Doctrine doesn't know what to do with it. The first try might be to set up custom DBAL types to handle the conversion from and to the PurchaseOrderId
type. This leads to a lot of boilerplate code for every custom attribute type we have, while there's a much simpler solution at hand. We just need to apply the following rule: only keep primitive-type values (string, integer, boolean, float) in the attributes of a Doctrine entity. That way, we don't need to do any complicated mapping, and we can just use Doctrine's basic types, string
, integer
, etc. Whenever you still need the value object, you just create it again (see supplierId()
below):
/**
* @ORM\Column(type="string")
* @var string
*/
private $supplierId;
private function __construct(
PurchaseOrderId $purchaseOrderId,
SupplierId $supplierId
) {
// ...
// convert to a string
$this->supplierId = $supplierId->asString();
}
public function supplierId(): SupplierId
{
// and back to an object, if necessary
return SupplierId::fromString($this->supplierId);
}
When it comes to the identifier: in this case we define the identity ourselves (we don't wait for the database to return an auto-incremented integer), and we should tell Doctrine about that:
/**
* @ORM\Id()
* @ORM\GeneratedValue(strategy="NONE")
* @ORM\Column(type="string")
* @var string
*/
private $id;
private function __construct(
PurchaseOrderId $purchaseOrderId,
SupplierId $supplierId
) {
$this->id = $purchaseOrderId->asString();
// ...
}
Finally, we need to configure the one-to-many relationship between order and lines. Doctrine requires us to use a Collection
for the lines, and it also requires the owning side of the relation (which is the "many" part), to carry a reference to the inverse side (the "one" part). In our case, the Line
needs to have a reference to the PurchaseOrder
it belongs to. This means we need to modify the addLine()
method a bit too:
/**
* @ORM\OneToMany(
* targetEntity="Line",
* mappedBy="purchaseOrder",
* cascade={"PERSIST"}
* )
* @var Collection|Line[]
*/
private $lines;
private function __construct(
PurchaseOrderId $purchaseOrderId,
SupplierId $supplierId
) {
// ...
$this->lines = new ArrayCollection();
}
public function addLine(
ProductId $productId,
OrderedQuantity $quantity
): void
{
$lineNumber = \count($this->lines) + 1;
// we also pass $this (the PurchaseOrder) to the Line:
$this->lines[] = new Line($this, $lineNumber, $productId, $quantity);
}
Note that, although we have to use a Doctrine Collection
internally, we can easily hide that fact and still return an array to clients of PurchaseOrder
:
public function lines(): array
{
return $this->lines->toArray();
}
For the Line
class we need to perform more or less the same steps. However, there are some interesting things to note:
- In our model we don't need or want
Line
to have its own ID, but Doctrine requires it to have one. So we just add it as a private field (it's not a problem to make this one an auto-incremented integer), and we never expose it as part of the object's API. - In our model,
Line
is a child entity ofPurchaseOrder
. For Doctrine, it's like any other entity; you could fetch separateLine
objects from anEntityRepository
if you like. We can make it clear that this should not happen by only defining interfaces and implementations for aggregate repositories, so we'll have aPurchaseOrderRepository
interface, but not aLineRepository
interface. Still, you could always talk to theEntityManager
directly and retrieve separateLine
objects from it, but... you just shouldn't do that.
The Line
class, when it has been converted to a Doctrine entity, looks like this:
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
final class Line
{
/**
* @ORM\Id()
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
* @var int
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="PurchaseOrder")
* @var PurchaseOrder
*/
private $purchaseOrder;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $lineNumber;
/**
* @ORM\Column(type="string")
* @var string
*/
private $productId;
public function __construct(
PurchaseOrder $purchaseOrder,
int $lineNumber,
ProductId $productId,
OrderedQuantity $quantity
) {
$this->purchaseOrder = $purchaseOrder;
$this->lineNumber = $lineNumber;
$this->productId = $productId->asString();
$this->quantity = $quantity->asFloat();
}
// ...
}
And all of this works well; you can store these objects without any trouble in a relational database. Summarizing:
- Attributes only contain primitive-type values. If a client needs the value objects, reconstruct them when needed.
- Child entities need their own ID; just give it to them.
- The owning side in one-to-many relations needs to carry a reference to the other side.
What about embeddables, custom DBAL types, life-cycle event subscribers?
There are plenty of almost-solutions to the make-DDD-work-with-Doctrine problem, but in my experience they complicate things a lot. Look at the above examples; everything is so simple; hooking into Doctrine ORM or DBAL internals isn't going to make it much better. All you need is short, one-line transformations between your rich domain objects and the simpler value types that Doctrine understands, and you're done.
Doctrine wants whole objects, not just IDs
As you know, Doctrine makes relations between entities by passing around entire object references (not just IDs). But, as you may also know, one of the aggregate design rules is: Reference Other Aggregates By Identity:
Prefer references to external aggregates only by their globally unique identity, not by holding a direct object reference (or “pointer”).
Vaughn Vernon, "Effective Aggregate Design, Part II: Making Aggregates Work Together" (PDF)
How could this ever work with Doctrine ORM? Well, it Just Works. You need to follow the advice, and you're done already. The example above shows how it works: you pass in the ID to an external aggregate (in this case SupplierId
), and set it - that's it.
What about foreign constraints then?
Without mapping the relationships using the real objects, we don't have the automatic convenience of Doctrine creating foreign key constraints for those relations. So, what about them?
Here's a challenging idea: I don't think you need foreign key constraints at all.
But if you want to, you can still enforce them of course. Just do a manual schema migration to add the index. You probably need an index anyway, to speed things up on the query-side (you can add them through Doctrine's mapping configuration too if you like).
Annotations - they pollute my clean domain model!
"I have this nice and clean domain model, no sign of external dependencies, no sign of persistence. And now we have these ugly annotations. Can we please get rid of them?"
Yes, of course, you could move the mapping configuration to some place else. To a Yaml file for example. Although the Doctrine team has decided to drop support for Yaml. But there's always XML.
Anyway, I don't think you should feel too bad about these annotations. It's very useful that they are right next to the things they configure (the attributes, and how they map to database columns). But also, you need to adapt your domain model to your ORM anyway (e.g. use Collection
s, pass the entire root entity into its child entities, etc.). So a few annotations seems to me like quite an innocent addition to this list of necessary changes.
"DDD" says: one transaction should persist only changes made to one aggregate
From DDD we know that every transaction may contain only the changes made to one aggregate:
A properly designed aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction. And a properly designed bounded context modifies only one aggregate instance per transaction in all cases.
Vaughn Vernon, "Effective Aggregate Design, Part I: Modeling a Single Aggregate" (PDF)
This is a bit of an issue, when we compare this to how Doctrine ORM works:
The state of persistent entities is synchronized with the database on flush of an
EntityManager
which commits the underlyingUnitOfWork
. The synchronization involves writing any updates to persistent entities and their relationships to the database.
A flush will persist all changes to any entity that was created and persist()
-ed, removed or modified, up to this point in time. So you need a way to overcome this problem.
There are usually two kinds of solutions: technical solutions and people solutions. In the first category you may consider using dedicated EntityManager
s for each aggregate type. This may be a bit too much though, although it's quite doable, but hard to maintain. Another technical solution would be to at least always call EntityManager::clear()
after a flush()
, which would force the entity manager to let go of any entities it would otherwise revisit upon the next flush()
.
The better solution is to consider the people solution: just don't persist changes to multiple aggregates at once. This requires discipline, and maybe a bit of redesign every now and then. But your domain model will end up in a better shape (if you ask the DDDeity, that is). Also, since it's a people solution now, you can deviate from it, and still persist multiple aggregates in one go (when you're updating in batches, or as part of truly shady operations).
What about collections of value objects?
Both embeddables and DBAL types are famous for their inability to easily store collections of value objects. And Doctrine is not to blame for that - it's the relational database itself that doesn't allow multi-dimensional columns. So, if you're facing this situation, promote those value objects to child entities. This is the only slightly larger sacrifice that you may have to make, in the name of Doctrine.
In Doctrine 2 if you do below simple mapping:
all is fine. However, problems start if you have any entity that has to hold references to a few others like this:
then Doctrine will treat all the VO ids as identifiers of the
Department
entity and complain that theDepartment
has multiple ids in case you need to define a relationship to it from a parent entity. "Solution" to this is to create a duplicated classesDepartmentId
in each namespace and change the line in .orm.xml mapping file for the VO (duplicates) from<id name="value" column="id" type="string" length="36" />
to<field name="value" column="id" type="string" length="36" />
but this is crazy as the more entities in different namespaces need a relation toDepartment
the more you have to create these duplicates. So overall your solution is much better.Hi Matthias, Thank you for your article! Have a question related to this post and graphql: Don’t you think that if we do not have any relations between entities then we will have a headache with related objects fetching, filtering and search? Is it worth to make additional graphql layer for example written on node, that will combine api-calls to fetch and filter related objects?
It would actually make a lot of sense to do that. This amounts to applying CQRS to your model: the entities are only used when you want to make changes to your application's state. When you want to present data to any clients, like users, or GraphQL clients, then you provide a different model, one that suits your clients. Here you can load and combine anything you want and present it. The advantage with any API (including GraphQL-based ones) is that you don't expose your "write" model to clients, allowing you to make changes to it without breaking the clients.
Hi, thanks for your comment, and your English is fine. As for IDs, value objects, aggregates, I take the approach to aggregate design as explained in Eric Evans - Domain Driven Design, and Vaughn Vernon, Implementing Domain-Driven Design. I don't want to use Doctrine types, just primitive types in my properties to make the mapping simple. If I need value objects, I create them on-the-fly based on the primitive values. What do you mean by giving up on value object libraries?
This is the answer. Thanks
Maybe you can give me a push in the right direction:
I worked both with event sourcing and doctrine, but never with combination of both.
I want to start a CQRS project with "plain old" doctrine - so not event sourced aggregates.
But I indeed do want my aggregates to produce domain events and create/update my read models using those events.
But then the "Doctrine Entity" would need to be aware of my Event Store to store the emitted events, which seems... weird.
Is this a known, existing, real problem, or have I made up something weird? Maybe I'm seeing it from a wrong perspective?
I'd suggest defining your own repository interface and implementation. The repository class would have a
save()
method, and inside that method you would retrieve from the entity the events that it has recorded. You would then save the entity using the Doctrine repository or entity manager that your repository gets injected as a dependency. And you would also save the events to the event store (whatever implementation you use for it). To prevent inconsistencies between the entity state and the events stored for it, you should wrap these steps in a single database transaction.That is a great advice, and well explained. Thank you. I'll set up an experiment and publish my results if anything interesting happens :)
You're welcome; let me know how it went!
I did it in a silly way for a test - just adding OneToMany relations to "events" in my entities. Say I have an "Item" entity, then it can have multiple "ItemEvents". That could be seen as "stream per aggregate type". Then, business methods in my entity-aggregate just add "new ItemEvent" to collection, and it's cascade persisted.
Events have type, json payload and some metadata in separate columns - each "aggregate type's" events can have different schema in DB, which is handy and probably very wrong :)
I feel this is far from optimal, but seems "backwards compatible" with classic "flat-state-persistence" and I can use them as I wanted - to project read models. I'll try to post some updates if it
explodesworks ok.(deleted)
Annotations are merely a convenience and do not lead to any coupling as they're just comments. In other words, you can keep using the same business entities even if you decide to opt out of doctrine in the future. If you still prefer to not define your mappings using annotations, you can use an external xml file to specify the mapping definition.
Hi @matthiasnoback:disqus ,
As usual your posts are very useful and I use them always as a reference ;) But I do have another use case which I would like to solve, and hoping you could shed some lights on it.
Let me take your tactical ddd workshop example of the MeetupGroup containing a list of MemberIds.
What's the easiest way to persist/load this with Doctrine?
At this point I was only able to solve that via the hack described in "What about collections of value objects?" but it requires that internally in MeetupGroup you have a list of real Members. And that's not the point ...
Hope you have a solution for that.
Thanks
Thanks man. Well, maybe that model was a bit weird. I probably wouldn't store a list of member IDs in a group now. But still, if a Member would be its own aggregate, then it could keep a list of group IDs. Anyway, as a modeling exercise, you could think about what your solution would look like if you didn't use an ORM, and would use events only. When does a user become a member of a group? When they join it. So, you could have
user.joinGroup(groupId)
, which produces an event,UserJoinedGroup
, which is the thing that gets persisted (if you'd use event sourcing). This is the write side of things. If you look at the read side, you could build up a read model just for the purpose of figuring out which groups a user belongs to.Important note: there's no table for "memberships", just the event store. Maybe there is for the read side though. The clue here is to consider those memberships to be a simple list of IDs (group IDs in fact), which, if you do have a write model that uses tables to for storage, you could store in a separate table. That table only has references (but doesn't use reference/integrity constraints). When you load a user, you won't load all its groups, or make those groups accessible in any way, you only load that list of group IDs. That's what I mean by using a collection of value objects: it will be a collection ofMembership
value objects, which wrap aUserId
and aGroupId
. If you want, you could even store membership in a serialized table column.Hope this helps!Thanks for taking the time to provide an answer. Meanwhile I pretty much did what you wrote down now :)
See you soon (PHPBenelux?) :)
Hehe, nice! Hopefully, yeah!
Hi Matthias, as usual thanks for taking the time to share your knowledge and experience.
I think we already discussed a very long time ago DDD and Doctrine, by the time embeddable were not even on the doctrine roadmap.
From my php/Symfony/Doctrine and DDD experiences:
“Don’t fight your framework” from the big guy is my golden rule. So like you do, I try to find compromises between the tooling and the architecture. (After all that’s also one of php asset: freedom/flexibility)
Regarding the foreign constraint. I’m mixed up. Lot of project uses (My)SQL because it’s an established technology even if other storages would fit, the cost to run a MySQL without being a DBA and to stretch MySQL into suboptimal usages is “good enough”.
I would say: if you drop the key constrain to comply with your software architecture you might ask yourself why would you rely on the relational storage? moving the integrity to your app is exactly what most of the people didn’t want to do by doing “apt-get install MySQL server”.
But I totally agree with you about respecting the boundaries of your aggregates. Otherwise you will end up with a very big coupled mess.
Since I do not want to fight my framework I would definitely add: it really depends on your use case. Sometimes I would favor cheating a bit DDD boundary in favor of the DB because the integrity of this data is highly highly critical to my business.
Some other time I would just pass a flat id, specially helpful when moving this growing aggregate to become is own microservice later. (And Yes: foreign key = data coupling = you are screwed when it comes “to microservice it”)
When it comes to multiple aggregates transactions/manipulations, well if I’m correct the app layer is fully qualified for this job: coordinate your domain (when multiple aggregates are in action). As soon as more than one aggregate is involved, I implement in the app layer. Calling a method there would ensure a correct “global domain transaction” while inside the method I wrap each aggregate into their owns. I find this extremely clean and maintainable.
The Line class __construct takes whole $purchaseOrder reference, not just an identity, otherwise doctrine won't accept PurchaseOrderId, as PurchaseOrder is expected for many-to-one reference. But this confronts with: "Reference Other Aggregates By Identity".
Thanks for pointing that out, but it's only seemingly a conflict: Since PurchaseOrder and Line aren't separate *aggregates*, we still follow the rule.
If you wouldn't use Doctrine, you wouldn't have to provide the whole PurchaseOrder to the Line constructor, but doing it for Doctrine isn't the worst sacrifice to make; as long as Line itself doesn't actually rely on any information that's on the PurchaseOrder entity.
Sorry, that question is not about topic, but - can you share your experience about Query Bus ?
There are a lot information about Command Bus, middleware around them, dispatching to queue etc.
But query bus experience in real life hard to find (examples with doctrine repositories integration etc.)
That's okay. My answer is: don't use a query bus. You can ask the query directly to the thing that gives the correct answer, that is, the repository.
Lol ;)
I think it like a command bus, can not applies everywhere.
But for example it will some statistic report with a lot of filters, data sources, aggregates...Maybe it will be better to through Query with filters/metrics/aggregates via middlewares (can apply doctrine filters for example) to handler, which fetch data from different repositories ?
Or maybe CQRS... if your data are projected very differently from the each view/model etc
Well, what you're looking for is sort of a façade to hide the implementation details of how the application arrives at the answer to your query. But this has always been possible with a simple interface. I do like to use a separate Query object sometimes, to keep all the aspects of the query together in one object (e.g. filters, pagination, etc.).
Thank you so much for this article. It's another great reminder for why I'm building an alternative for Doctrine :)
Hehe, I'm not sure that is the conclusion I was looking for, but good luck!
I have been working with embeddables so far, but I like this way of doing things as well.
I had some quick questions though:
How do you deal with value objects consisting of multiple fields, let's say an address containing a street, number, postal code and city. Would you just add 4 fields to the entity and then transform those to/from the Address value object?
Also if you want to for example check if a new value passed in equals the value in the current state of the entity, you would transform the current state in a value object before comparing and not compare primitive values I assume?
Also thanks for all the interesting articles on this topic Matthias, your articles on DDD, hexagonal architecture and other topics have helped me grow as a developer.
I think I know why Matthias avoid relying on doctrine complex features, DDD is an architecture to answer project complexity.
With or without DDD, I’m not sure if you used doctrine inheritance. It’s been there since v1 php ORM generation and Benjamin Eberlei (he runs doctrine ORM project) wrote about it something that sounded like: please stay away from this hell.
Embeddable are nice, for sure, yet remember their cost, their limitations and their incompatibilities with Value Object: a very simple example, you have a nice value object called Address, and another one called Geocoords. What do you do when your domain requires you to design a Value Object called Place that should have an address and geocoords? You already used your embeddables in many entities and you don’t want to tweak them for the sake of complying to Doctrine limitation. So suddenly you will take one compromise and that might be exactly what Matthias is using as a standard: build this on the fly. CPU and memory won’t be so much affected. They day your performance suffers from generating a bunch of VO will never come.
I was having a similar reflexion around this concept of DDD using Doctrine earlier this week.
I came up with the idea of keeping domain model completely ignorant of the infrastructure by defining doctrine entities in the infrastructure layer and use a trick to map them back into my domain model.
You may get a glimpse of it here: https://gist.github.com/gqu....
The idea was to slowly bring the DDD concepts into an existing team/application and not go full CQRS / ORM-less from the beginning.
I must say I wasn't really convinced by the result as it felt like I was redevelopping the Doctrine object hydrator, especially to handle relations...
Another idea would be to extend domain entities into doctrine entities which would be defined in the infrastructure layer (and putting this auto increment id here).
Technically, that wouldn't break the repository interface for instance, domain object instances would be returned.
I haven't tried it yet though.
You are trying to find “the standard way” wrapper over an ORM and DDD.
You are super brave. Remembers that ORMs failed to try to find a standard way between graph and tables themselves :)
I was looking for this pure harmony between the DDD software architecture and philosophy that urged me to design everything extremely specifics to each part of my projects while enjoying the benefits of a scaffolding, RAD, build to fit all tools.
Despite the incredible features and quality of Doctrine as an ORM in PHP. The answer is in the quest itself.
Architectures are agnostic. Most of DDD examples would be found in Java. Persistent / in-memory object graphs... that’s already one thing we are deprived from, oh yes I was so jealous when I was struggling using my Doctrine Entities as pure DTO feeding my Domain ones.
I’m not saying to loose hope and stop digging. Because what you are doing may bring some innovations into our tools. But if you need a pragmatic answer (and a bit boring) for you next business stressed project: no silver bullet.
Our job is also to leverage our tech strategy mixing a bunch of tools and a bunch of architecture and try to find the best way to use them in some parts of our projects while dropping them in some other parts.
Sometimes I successfully implemented a DDD entity (aggregate rarely) into a single Doctrine entity. Sometimes I used Doctrines entities to just be a marker from my annotation schema and would just pull flat arrays from repository to feed a tailor-crafted DDD Entity (basically I didn’t even care to hydrate these Doctrine entities because it would just bring negative performance and my domain entity was so complex that Doctrine was long gone when we started to draw the graph on paper)
What a mess heh? When it comes to maintains, defines standards. Matthias have one: no embeddable, scalars and primitive in his doctrine entities.
Find them sprint after sprint, share them with your team, change them if needed. If you want to go full DDD, well you know what to do: php OO is enough. If you want to save time with your infra layer etc. Careful :)
Hey, thanks for sharing that idea; I've been watching a team that did it (separate Doctrine entities and "DDD" entities) - it's not great, just a lot of extra trouble. Like I mention in this article, if you really want to use Doctrine, just mix it with your "pure" DDD code. Then, if Doctrine gets in the way too often, just take that nice nuclear option :)
@matthiasnoback:disqus,
A couple questions. First, could you elaborate a bit about the issues the team you mentioned were running into with the approach @gildasqumner:disqus suggested?
Second, what about taking a simpler approach that doesn't involve trying to mess with the way Doctrine serializes and deserializes objects? Here is what I am considering:
Define entities, value objects, and aggregates in my own domain code. Plain objects, not Doctrine entities. Define my own repository objects that internally call a Doctrine repository. _My_ repository objects would return _my_ domain objects, but the data my domain objects are hydrated with would come from the internal calls to the Doctrine repository.
The general idea here would be to "wrap" Doctrine objects and services in my own domain code, hopefully offering the following benefits:
- my domain objects would not leak any connection to the underlying Doctrine entities, thus removing coupling at the application layer to the specific ORM in use
- my domain objects could take value objects as their attributes; mapping the value objects' underlying primitive values to Doctrine entities would be entirely contained in my repository
- no annotations, embeddable types, or monkeying with Doctrine mapping beyond very basic requirements
Is this a reasonable approach? It means a bit more code overhead but to me the tradeoff seems fine; do a little more work under the hood for better encapsulation? My domain doesn't and shouldn't care or know about Doctrine, so why even pass Doctrine entities around in my domain model? What do you think?
I've been thinking about this, and one of the problems I'd run into eventually is that wrapping a Doctrine repository with my own "domain" repository would require me to duplicate the in-memory pool of objects already retrieved from the datastore. Keeping my own "domain" repository in sync with Doctrine's would be a huge nightmare.
I've been experimenting with a CQRS-style app based on the recommendations you shared in this post, and things are working out very well. Thanks again for this excellent advice.
I am still curious to see what problems the team you mentioned in your previous reply ran into.
Happy to hear that it's working out well!
You could reach out to https://twitter.com/rpkamp82 for more details about that effort. I really don't believe it's worth all the extra effort. Keeping the domain free of persistence-related code is a dream, even in my more recent experiences with "ORM-less" (https://matthiasnoback.nl/2...). Actually, I find this ORM-less approach much more clean than anything else I've encountered so far.
Thanks, that post is extremely interesting! I added a comment to that post: http://disq.us/p/1w4bsj8
Hey Mathias! Thanks for the blog post, although I don't agree with every statement.
Primitive types in entities - are you sure that the domain will stay clear this way, independent from infrastructure? Or is this just a convenience tradeoff, to save some typing? I think the typing is worth the effort, so the domain is simpler that way.
Annotations in entities - your argument is about the need of adapting the domain model to Doctrine (e.g. collections - it should be possible to use custom collection in Doctrine 3... then root entity passing which is possible to avoid using One-To-Many, Unidirectional with Join Table associations, which isn't an optimal solution though) ... but still wouldn't the domain be clearer with less infrastructural information instead of more?
Foreign constraints - IMO yeah, you are right, foreign constraints should not be really needed between aggregates, but still it is quite nice to have them, just for the data integrity sake ... I just got quite a good idea there (just not implemented yet), because in our company we use different read and write models (Doctrine entities) mapped to the same database tables. Currently we're generating the schema from the write model (without cross aggregate associations and constraints), but we still can do that using the read model, where the associations (and foreign constraints) might come in handy :)
One transaction per aggregate - I agree with the theory, but this is the only case where we aren't able to align with the theory. Our use cases are quite complex, each command may trigger changes in 3 or 4 aggregates, so we shifted the transaction boundary to the main user command, which is triggering the whole process. Using one transaction per aggregate would require to implement quite many failed transaction compensation routines, which would be quite expensive from the production point of view. What do you think about that?
And regarding value objects in collections - it is always possible to use custom type, storing the data as json ... but yes, it requires some boiler plate
Hey there, interesting comment there. You made me curious, do you care to explain a bit the setup of multiple entities for one schema, I’ve been wondering about this since a long time.
How do you specifically setup this and also how do you ensure the sync of all entities on a migration case? Aside of unit testing all your read entities (which sounds costly). And globally I’m really keen to get your feedback on this setup (pro/cons/pitfalls)!
Regarding your question about aggregate boundary (atomic/transaction), as soon as you need to coordinate more than one aggregates, go inside the app layer and implement there. Services, Command Handlers, name it according to your architecture. People opinions are mixed up. But to me it’s the best way to standardize these needs while ensuring app transaction and aggregate transaction boundaries. And I can’t quote it, but it’s also an official answer from DDD (or Vernom).
Last: collection usage or despite advanced feature is an absolute no go for me. And really I don’t see any case aside the basic Entity-Child Entities like Invoice-Lines where collection won’t fire back at me later. As soon as you need to filter, partially fetch, I would highly recommend to not use the collection thingy as a way to say “heh I can’t inject anything into my Doctrine entities properly but at least collection magic is here for me”.
Hi, I'm not sure whether this is best practise, but I can describe it anyway. The process is error prone, so I'm not sure whether its a good idea to recommend it.
Our intention was to separate the write and read model, to be able to have only domain rules in the write model and to be able to get rid of almost all the unneeded getters...
The setup goes this way:
1) We create our write model entities
2) we create yaml mapping for these entities
3) we use this model for doctrine migrations, so the schema gets generated from this data
4) then we create our read model entities, with getters, which are used for client respone rendering (mostly some kind of json)..
5) I don't know whether there is a way to be able to validate the schema with the read model... maybe a doctrine schema validation command could be useful, but I'm not sure, since we use primitive types instead of custom types or embeddables in our read modesl . We don't really do this for now, since we have more important issues to solve.
6) We don't have time for unit tests for now, but we're planning to write at least some acceptance tests, to be sure that the system works as intended
Regarding the transaction boundaries - this is a tricky one, because our app is a really special case, which satisfies 2 of 4 conditions to break aggregate transactional consistency rules ... These conditions are written in Vernon's Red book ... But I didn't know about these rules when I wrote the previous comment :)
Regarding the collections - I agree, our models were a little bit wrong, we are breaking our aggregates into smaller parts and getting rid of the collections in most cases
Hi Mathias,Interesting blog post!If you want to avoid to give the reference of
PurhcaseOrder
to theLine
, we can use https://www.doctrine-projec....If you don't want to write custom type for doctrine, you can use https://www.doctrine-projec.... In my opinion, writing a doctrine is pretty quick and easy ;)ArnaudInteresting suggestion about the One-to-Many with a join table, thanks!
Embeddables are convenient, but I found that there are some drawbacks using those:
1. You can't add a @ORM\Id annotation to the @ORM\Embedded annotation, so you still need another solution for this (custom doctrine types are storing the primitive type in the entity)
2. Nullable has to be set on the Embeddable itself, so if for example an Address value object is reused in multiple places and is nullable in some cases and not in other cases, you would still have to set all columns to null and doctrine would initialize the value object even if all values are null when it is loaded from the db.
3. When writing DQL queries you end up with lots of 'object.email.email' in your select and where clauses. A minor inconvenience that can be avoided by writing SQL instead, but then you will probably end up with 'table.email_email' instead.
1 - I don't like and I don't use annotation :)
2 - If an value object related to several aggregate with different rule, I think it is more convenient to duplicate them.
3 - I am not fan of them and I don't use them. I prefer to write doctrine type.