REX DDD

Domain Driven Design in Symfony

... and more

Presented by Maxime Colin / @colin_maxime

Contexte

Refonte d'une application. 250 JH. Qualité et testabilité demandé par le client.

Architecture Hexagonale

Domain

Data Model

View Model

Repository interfaces

Adapters interfaces

Domain services

Domain

namespace VendorName\Domain;

Application

Business actions

Business logic

Business services

Adapters interfaces

Application

namespace VendorName\Application;

Infrastructure

Repository implementations

Persistence layer

Adapters implementations

Services definitions (DIC)

There is a bundle for that

namespace VendorName\Bundle\InfrastuctureBundle;

Doctrine config


doctrine:
    orm:
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_generate_proxy_classes: "%kernel.debug%"
        auto_mapping: false
        mappings:
            entity:
                type: yml
                prefix: Acme\Domain\Model
                dir: %kernel.root_dir%/../src/Acme/Bundle/InfrastructureBundle/Resources/config/doctrine/entity
                alias: Entity
                is_bundle: false
                    

UI

Symfony stuff

Controllers

Forms

Views

One bundle for each entry point

namespace VendorName\Bundle\AppBundle;
namespace VendorName\Bundle\ApiBundle;

CQRS

Command Query Responsibility Seggregation

Separate Read from Write

Models for read only purpose

Models for write purpose

Read

Hydrate your query results in read only data

SELECT new ArticleListView(a.title, a.author) FROM Entity:Article

Write

Use entities for write purpose only

Repository implementation

Do not use Doctrine repositories, inject EntityManager

Repository can be based on Doctrine, an API, memory, redis, ...

The persistance should not impact the domain

Framework agnostic code

Use command bus...

or request/response service...

or even manager

No Symfony dependencies

No business code in controllers

Easier to test

Comminicate with Symfony using...

Events

Exceptions

Use DTO for form model

Don't limit your form architecture to your entities model

Validation is easier

Protips

Comment domain model


/**
 * "Gabarit"
 */
class Pattern
{
}
                     

Form

Create reusable form type

Create form extension

Don't repeat yourself

SensioGeneratorBundle

Handle redundant development tasks by...

Creating your own code skeletons

Creating your own generate commands

Exception

Create exception for every errors you must handle

Throw general exception for others

Custom query builder

Reusable and chainable query builder

Put criteria/filter in method

Create many query builder as you want

Custom query builder


class ArticleQueryBuilder
{
    public function __construct(EntityManager $em)
    {
        parent::__construct($em);

        $this
            ->select('article')
            ->from('Entity:Article', 'article');
    }
}
                     

Custom query builder


class ArticleQueryBuilder
{
    public function isPublished(\DateTime $at)
    {
        $this
            ->andWhere('article.published = TRUE')
            ->andWhere('article.publishedAt < :at')
            ->setParameter('at', $at);

        return $this;
    }
}
                     

To infinity and beyond

Specification

Put your business rules in reusable classes

See maximecolin/satisfaction

More DDD

Less getter/setter

More domain method

Replace

$article->setPublished(true);
$article->setPublished(false);
                    
by

$article->publish();
$article->unpublish();
                    
Replace

$product = new Product()
$product->setTitle('Lorem ipsum');
$product->setDecription('Foobar');
$product->setPrice(15.99);
                    
by

$product = new Product('Lorem ipsum', 'Foobar', 15.99);
                    

Doctrine embeddables

Doctrine 2.5


/** @Entity */
class User
{
    /** @Embedded(class = "Address", columnPrefix = "myPrefix_") */
    private $address;
}
                    
SELECT u FROM User u WHERE u.address.city = :myCity

UUID

216f-ff40-98d9-11e3-a5e2-0800-200c-9a66

Symfony

Controller as service

YAML routing

No @Template, @Route, ...

Questions ?