Domain-Driven Design in a Nutshell I.

Strategic Patterns

  • Bounded Context
  • Ubiquitous Language

Tactical Patterns

  • Onion Architecture
  • Entity, Value Object
  • Repository
  • Services

Strategic Patterns

  • Ubiquitous Language
  • Bounded Context

Ubiquitous Language

"The Ubiquitous Language is a shared language developed by the team—a team comprised of both domain experts and software developers."

Ubiqutious Language

interface User
{
    public function changePersonalName(string firstName, string lastName);

    public function relocateTo(PostalAddress changedPostalAddress);

    public function changeHomeTelephone(Telephone telephone);

    public function changeMobileTelephone(Telephone telephone);
}

Bounded Context

Bounded Context

Bounded Context

Concepts can be modelled with different granularity

Bounded Context

Generic vs. Core Domain

Bounded Context

Context Mapping helps you to orient between the different models

Bounded Context

They should be the foundation of the micro-service architecture

Tactical Patterns

  • Onion Architecture
  • Entity, Value Object
  • Repository
  • Services

According to Evans, they should have been in the appendix

Tactical Patterns

They are still very useful and instructive

They foster putting business logic into the center of the software

Tactical Patterns

They can make your programs easy to unit-test

Tactical Patterns

They even give advices about performance optimization

Tactical Patterns

But in return...

Tactical Patterns

You need more layers

Tactical Patterns

You need much more classes

Tactical Patterns

You have to practice a lot

Tactical Patterns

Development time becomes longer

Tactical Patterns

But maintenance becomes shorter

Refactoring becomes less risky

Onion Architecture

Entity, Value Object

The concepts of the domain

Product

Customer

Address

Shop

Inventory

Image

PhoneNumber

Cart

Order

Article

Order

Shipping

BankAccount

Entity, Value Object

Plain Old PHP Objects (POPO)

Entity, Value Object

They only have Entity and Value Object depenencies*

Entity, Value Object

They enforce the business rules

Otherwise we talk about Anemic  Domain Model

Entity, Value Object

Entity: identifier equality

VO: structural equality

class User
{
    /** @var Uuid */
    private $id;

    /** @var string */
    private $name;

    // ...
}
class EmailAddress
{
    /** @var string */
    private $email;

    // ...
}

Entity, Value Object

Entity: mutable

VO: immutable*

class User
{
    public function changeName(NameChange $nameChange)
    {
        $this->name = $nameChange->getName();
    }
}
class EmailAddress
{
    public function withEmail(string $email): EmailAddress
    {
        $self = clone $self;
        $self->email = $email;
        return $self;
    }
}

Entity, Value Object

PSR-7:

class ProductController
{
    public function redirectToNewUrl(ServerRequestInterface $request, ResponseInterface $response)
    {
        $response = $response->withStatus(301);
        $response = $response->withHeader("Location", "https://example.com/products/123");
        
        return $response;
    }
}

https://github.com/moneyphp/money

Money:

$jim_price = $hannah_price = Money::EUR(2500);
$coupon = Money::EUR(500);

$jim_price = $jim_price->subtract($coupon);
// $jim_price->subtract($coupon);

$jim_price->lessThan($hannah_price); // true
$jim_price->equals(Money::EUR(2000)); // true

Entity, Value Object

class User
{
    /** @var EmailAddress[] */
    private $emails = [];

    public function addEmailAddress(NewEmailAddress $newEmail): void
    {
        $email = $newEmail->getEmailAddress();

        if (isset($this->emails[$email->__toString()])) {
            throw new EmailAddressAlreadyExists();
        }

        if (count($this->emails) >= 15) {
            throw new TooManyEmailAddresses();
        }

        $this->emails[$email->__toString()] = $email;
    }

    public function removeEmailAddress(EmailAddress $email): void
    {
        if (isset($this->emails[$email->__toString()]) === false) {
            throw new EmailAddressNotFound();
        }

        if (count($this->emails) <= 1) {
            throw new TooFewEmailAddresses();
        }

        if ($email->isPrimary()) {
            throw new EmailAddressCantBeRemoved();
        }

        unset($this->emails[$email->__toString()]);
    }
}

Repository

Let's abstract away the persistence layer!

Repository

It will be easy to change DBMS

It will be easy to write unit tests

It will be less hard to change DBMS

Repository

"In-memory like collections"*

Repository

interface ProductRepositoryInterface
{
    public function getProductBySku(string $sku): Product;

    public function getProductList(ProductListing $productListing): ProductList;

    public function saveProduct(Product $product); //public function addProduct(Product $product);

    public function deleteProduct(Product $product); //public function removeProduct(Product $product);
}

The interface is part of the Domain Layer

Repository

class EloquentProductRepository implements ProductRepositoryInterface
{
    // ...
}

class FakeProductRepository implements ProductRepositoryInterface
{
    // ...
}

Implementations are part of the Infrastructure Layer

Repository

General vs. Specialized

interface ProductRepositoryInterface
{
    public function getProductBySku(string $sku): Product;
    
    // ...
}
interface RepositoryInterface
{
    public function getModelById($id): AbstractModel;
    
    // ...
}

Repository

Domain objects are returned*

Repository

You can use black magic via a Data Mapper ORM

... or Factories with the Composite Pattern

You can use an Active Record ORM

Repository

But...

Repository

How may objects would you like to get when you let's say retrieve a list of books along with their authors and publishers?

Repository

As many Entities and VOs the list has

As many different Entities and VOs the list has

Repository

Repository

Identity Map For The Win

Repository

Ensures that an object is loaded only once

Repository

  • Windows, Virtualbox, Docker

  • PHP 7 + Opcache

  • ~200 KB of JSON response

  • 50 main entity

  • 4 relationships by entity

Without Identity Map:

With Identity Map:

Service

Application Service vs. Domain Service

Domain Service

Involves business processes

Domain Service

Combines tasks that involve multiple entities

Domain Service

Stateless

Application Service

~ Controller

Application Service

Request -> λ -> View

Request -> λ -> Response

Application Service

class AddEmailAddress extends AbstractService
{
    /** @var MeRepositoryInterface */
    protected $meRepository;

    /** @var EmailAddressVerificationRepositoryInterface */
    protected $verificationRepository;

    /** @var EmailServiceInterface */
    protected $emailService;

    public function execute(JsonApi $jsonApi): ResponseInterface
    {
        $userId = $this->config->getCurrentUser()->getId();
        $newEmailAddress = new NewEmailAddress($jsonApi->request->getResourceAttribute("email_address", ""));

        // Add email address
        $me = $this->meRepository->getMeById($userId);
        $me->addEmailAddress($newEmailAddress);
        $this->meRepository->saveMe($me);

        // Create email verification token
        $verification = EmailAddressVerification::generate($me, $newEmailAddress->getEmailAddress());
        $this->verificationRepository->createVerification($verification);

        // Send email verification email
        $this->emailService->send(new EmailAddressVerificationEmail($verification));

        // Respond with 204 No Content
        return $jsonApi->respond()->noContent();
    }
}

Conclusion

Why to use DDD?

Conclusion

Encourages collaboration with Domain Experts

Conclusion

Uses the power of the language

Conclusion

Makes the business logic explicit

Conclusion

"DDD: object-oriented programming done right"

What's next?

Domain Events

CQRS

Event Sourcing

Event Storming

...

Aggregates

Resources

  • http://www.drdobbs.com/architecture-and-design/domain-driven-design-the-good-and-the-ch/240169117
  • http://www.informit.com/articles/article.aspx?p=1944876&seqNum=3
  • http://martinfowler.com/bliki/BoundedContext.html
  • https://blog.codecentric.de/en/2015/04/bounded-contexts-and-data-duplication-in-action-adding-a-shop-system-to-the-movie-database/
  • http://www.infoq.com/articles/ddd-contextmapping
  • http://www.martinfowler.com/bliki/AnemicDomainModel.html
  • http://verraes.net/2016/02/type-safety-and-money/
  • http://enterprisecraftsmanship.com/2016/09/22/email-uniqueness-as-an-aggregate-invariant/
  • https://coderwall.com/p/wngbng/building-an-identity-map-in-php
  • https://igor.io/2013/02/03/http-foundation-value.html
  • http://blog.mirkosertic.de/architecturedesign/dddexample
  • http://blog.sapiensworks.com/post/2016/05/12/ddd-is-expensive-myth

Domain-Driven Design in a Nutshell I.

By Máté Kocsis

Domain-Driven Design in a Nutshell I.

  • 1,233