SymfonyCon follow-up: Doctrine entities vs Domain entities

After doing my talk on Domain-Driven Design at SymfonyCon, I was talking to someone who was in the audience and who wondered about how to work with Doctrine entities vs Domain entities.

If you want to have a puristic approach to DDD (especially when you do the common combination with a hexagonal archicture), then your Domain entities should not leak infrastructure and they should be specific to your Domain and your Bounded Context. The domain entity should not care about how it is stored in the database (or wherever you store the data). So if you take the puristic approach, this means that you should have classes that constantly convert your Doctrine entities into domain objects and the other way around.

If I take the example I use in my talk of a Payment, then in the bounded context of the Checkout, the Payment only needs some basic information about the payment (amount and status, for instance). But in the Accounting context, we need a lot more information to correctly process that Payment into our books, so in that context the domain entity should have a lot more information such as payment method, perhaps even credit card or bank account details, etc. If you store that in a single database table, your Doctrine entity will have all that information available, but the Domain entities should only limit themselves to the information you actually need there.

And while the puristic approach is the best approach, I also understand that in a lot of projects you do not have the time to actually take this approach. And it actually is possible to keep using Doctrine entities and still do this relatively cleanly in your codebase. The trick? Interfaces.

In your Domain layer you define your interface, for instance:

namespace Webshop\Checkout\Domain;

interface CheckoutPayment 
{
    public function getStatus(): PaymentStatus;

    public function getAmount(): Money;
}

and in Accounting:

namespace Webshop\Accounting\Domain;

interface AccountingPayment 
{
    public function getStatus(): PaymentStatus;

    public function getAmount(): Money;

    public function getPaymentMethod(): PaymentMethod;
}

Now, your Doctrine entity implements these interfaces:

class Payment implements CheckoutPayment, AccountingPayment
{
    // here you just implement everything you need for your entity
}

In your application code, you just typehint the correct interface: In your Checkout context you typehint on the CheckoutPayment and in your Accounting context, you typehint the AccountingPayment. Yes, now the information from another context could potentially leak into the wrong context, but since we typehint on the interface we can now use a tool such as PHPStan to scan for places where we incorrectly use information that is not meant for that context.

My preference

While it is more work, I usually prefer to have separate classes for transforming Doctrine entities into Domain objects. Another reason for that is that sometimes you may need to want/do some additional transformations into Value Objects, or have an Anti-Corruption Layer between "outside" data sources (which could be other bounded contexts, but also the database could be seen as an outside data source). But this approach is certainly overkill for quite a few situations. In those situations, working with the interfaces in the Domain and your Doctrine entities directly in your code is a fine solution. It's defintely sometimes recommended to be pragmatic in your code. What might be considered a shortcut in one project is a perfectly fine solution in another project. And especially if your dilemma is "I either work with this pragmatic DDD-ish approach" vs "No room for DDD in this project", add that extra structure in a way that fits your project. It will help you out later.