Domain-driven-design: Writing domain layers. The fast way.

Roland Franssen
Feb 27, 2018 · 6 min read

And how to decouple your business logic.

In this post I’ll explain how I write any <insert name here> domain layer for my applications these days.

Before we begin some random requirements:

  • own all the business logic on our side; not the vendor side
  • decouple from vendor code; keeping it an implementation detail
  • pick vendors wisely; based on added value
  • a SOLID design; no cheating
  • KISS / DRY; stating the obvious here
  • PROFIT 😉

Overall we want to avoid any boring stuff.

The domain layer

Next we’ll need a concrete subject to demo with … the thing our domain layer is about. To keep it simple I choose good old cars 🚗, roughly described as:

  • A Car entity identified by a unique model name and year. It’s tied to a single Brand.
  • A Brand entity identified by a unique identifier value. It holds many Cars.

Depending on the complexity / real-life practice of the domain layer there are more design choices to be made upfront:

  • What type of identifier should the Brand entity have? An integer… UUID maybe?
  • Do you expect multiple persistence layers? Anticipating either a database, document store, etc. as used implementation.

Also from my experience, it’s good practice to be consistent in design across domains, at the application level. If you go with interface bound repositories, identifier value objects, etc. then stick to that. Not saying all entities should follow the same implementation; that’s where you vary.

The tools

Mostly plain PHP actually, given entities will be regular POPO objects. But as mentioned we’re going to skip any boring stuff and pick vendors wisely. Let’s start of with:

If you know these tools you might see where I’m heading first. That is a persistence layer for our entities, compatible with all common database vendors.


Now, for the purpose of the demo we’ll expect the number of car brands possible to be endless (which is not the case in real life 😉), and therefore our identifier will be based on a UUID value. We’ll add some more tools to our list:


Practically speaking we can go from here, however, the twist is that we most likely end up relying upon concretions — a UUID library, the Doctrine library and thus a vendor database — whereas this post is about decoupling; we want abstractions instead.

I hear you thinking. “Yes, I’d write those abstractions and implement them as necessary.”. But there’s often a gap between the application and working with the vendor code from our tool list. At some levels things become very tedious to do over and over again:

  • Writing Doctrine types
  • Writing Doctrine repostitories
  • Writing collections (as Doctrine’s collection is an implementation detail)
  • Writing identifier value objects
  • Factorizing entities

I was seeking some form of standardization …

The classic problem. Also inevitable.

​In general, there is nothing wrong with a little competition. Pick what fits your needs best, hence we’re going to give #15 a try:

The goal

Create an application tailored Car domain layer. Fast.

Start the clock!

First the identifier value objects. We’ll see the base domain layer immediately in action:

Nice, 2 implementations for free. A generic one working which works with any scalar value, as well as a UUID specific one; tailored to Ramsey’s UUID library.

Next, we’ll create the entities:

(Using Doctrine annotations for simplicity … after all those are just comments 😅 If you want to decouple from it use XML mapping instead)

Note how we treat $cars as a simple iterable type (implied by a [] suffix on the type hint). That way we allow Doctrine to populate it with its own Collection type without violations; it’s an implementation detail.

If the DomainCollectionInterface is too opinionated I suggest to switch to getCars(): iterable (returning Doctrine’s collection as array) or utilize a tailored CarCollection instead.

Moreover DomainCollectionFactory will take special care of Doctrine’s collection to keep leveraging it. Otherwise you’ll get a powerful generic collection with lazy traversal support for iterators.

Last but not least, we can conclude a Car can only be created if we have a Brand (makes sense). We avoid exposing the bi-directional relation in favor of one-way traffic.

The Doctrine type

As you might have seen in our entity annotation mapping we use this Doctrine type called brand_id. It’s needed to handle the translation between the database type and the one used in PHP; in our case any sub class of BrandIdInterface. Also, we have to built it:

And done 😉

OK. There’s one tiny thing; with the above type setup all that’s left to do is to let Doctrine know which type of theirs (the underlying data type so to speak) we’re actually using, and which class represents it. We can do it upon type registration, somewhere during bootstrap:

From here on we can simply switch the type on a per-case basis, practically using any type possible (think setDataType(Type::SOME_NAME)). By default it uses DomainId for a class with type Type::INTEGER.

The Doctrine repository

I know that one! $doctrine->getManager()->getRepository(Car::class) Right?

Yes. Basically. But in terms of decoupling we need to abstract it somewhere into our own repositories. For our demo we create the following (minimal) feature-set:

And the Doctrine implementation:

Done. Fully typed repositories downsized to just a few lines, primarily based on our own contracts; I like it 😏

What. Just. Happened?

From the base domain layer we leverage an internal repository trait, tailored to Doctrine; DomainEntityRepositoryTrait. It allows us to work with its entity manager in a fast and concise way when it comes to all basic operations.

Usage:

Stop the clock!

We did it! The App\Car domain is a thing. Not much left to do …

Final words

Yes, we have a basic car domain layer, but it’s just the first step.

The reason I wrote the base domain library, and started the Message driven PHP project itself was to actually leverage domain layers in a message driven way. Although not required, I personally fell in love with writing those “messages” and handling them, decoupled from anything else. Providing reusable business logic.

Think:

$messageBus->dispatch(new CreateCarCommand([
    'model' => ...,
    'year' => ...,
    'brand' => $brandId,
]);

Where CreateCarCommand is a so called “message”. It’s handled by a corresponding CreateCarHandler to do all the work, possibly dispatching other “messages” from there on. It creates a flow of business.

Think of CreateCarHandler dispatching a new CarCreatedEvent message after it’s done working, for you to subscribe to:

class CarCreatedSubscriber
{
    public function __invoke(CarCreatedEvent $event)
    {
        // notify about a new Car from $event->car
    }
}

Yes. You’d write those messages and handlers just as fast as we wrote our domain layer. Have a look at our documentation:

  • Docs (see domain-driven-design and message driven chapters)

The way we wrote our Car domain (decoupled) actually allows us to ship it as a standalone package. In that case we only need to change our Doctrine entity mapping to be mapped super classes, thus allowing to be sub classed and provided as entities at the application level.

Which is exactly what I’m doing for common domain layers 😉

Want that to be wired as a service in your Symfony application?

Last but not least; the next thing I’ll be working on is aiming to creating a “GraphQL Car API”. Backed by ElasticSearch, based on API-platform … and also just as fast.

Think about that 😉

Cheers!

edit: Continue reading the next part

Roland Franssen

Written by

Symfony Developer & Contributor, living and working in the Netherlands 🌷 / Purist / Opinionated / Into UX & DX / @https://msgphp.github.io