Domain Driven Design PHP helper classes

Related tags

Frameworks ddd
Overview

carlosbuenosvinos/ddd

Build Status

This library will help you with typical DDD scenarios, for now:

  • Application Services Interface
  • Transactional Application Services with Doctrine and ADODb
  • Data Transformers Interface
  • No Transformer Data Transformers
  • Domain Event Interface
  • Event Store Interface
  • Event Store Doctrine Implementation
  • Domain Event Publishing Service
  • Messaging Producer Interface
  • Messaging Producer RabbitMQ Implementation

Sample Projects

There are some projects developed using carlosbuenosvinos/ddd library. Check some of them to see how to use it:

  • Last Wishes: Actions to run, such as tweet, send emails, etc. in case anything happen to you.

Application Services

Application Service Interface

Consider an Application Service that registers a new user in your application.

$signInUserService = new SignInUserService(
    $em->getRepository('MyBC\Domain\Model\User\User')
);

$response = $signInUserService->execute(
    new SignInUserRequest(
        '[email protected]',
        'thisisnotasecretpassword'
    )
);

$newUserCreated = $response->getUser();
//...

We need to pass in the constructor all the dependencies. In this case, the User repository. As DDD explains, the Doctrine repository is implementing a generic interface for User repositories.

<?php

namespace MyBC\Application\Service\User;

use MyBC\Domain\Model\User\User;
use MyBC\Domain\Model\User\UserAlreadyExistsException;
use MyBC\Domain\Model\User\UserRepository;

use Ddd\Application\Service\ApplicationService;

/**
 * Class SignInUserService
 * @package MyBC\Application\Service\User
 */
class SignInUserService implements ApplicationService
{
    /**
     * @var UserRepository
     */
    private $userRepository;

    /**
     * @param UserRepository $userRepository
     */
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    /**
     * @param SignInUserRequest $request
     * @return SignInUserResponse
     * @throws UserAlreadyExistsException
     */
    public function execute($request = null)
    {
        $email = $request->email();
        $password = $request->password();

        $user = $this->userRepository->userOfEmail($email);
        if (null !== $user) {
            throw new UserAlreadyExistsException();
        }

        $user = new User(
            $this->userRepository->nextIdentity(),
            $email,
            $password
        );

        $this->userRepository->persist($user);

        return new SignInUserResponse($user);
    }
}

I suggest to make your Application Services implement the following interface following the command pattern.

/**
 * Interface ApplicationService
 * @package Ddd\Application\Service
 */
interface ApplicationService
{
    /**
     * @param $request
     * @return mixed
     */
    public function execute($request = null);
}

Transactions

Application Services should manage transactions when dealing with database persistence strategies. In order to manage it cleanly, I provide an Application Service decorator that wraps an Application Service an executes it inside a transactional boundary.

The decorator is the Ddd\Application\Service\TransactionalApplicationService class. In order to create one, you need the non transactional Application Service and a Transactional Session. We provide different types of Transactional Sessions. See how to do it with Doctrine.

Doctrine Transactional Application Services

For the Doctrine Transactional Session, pass the EntityManager instance.

/** @var EntityManager $em */
$txSignInUserService = new TransactionalApplicationService(
    new SignInUserService(
        $em->getRepository('MyBC\Domain\Model\User\User')
    ),
    new DoctrineSession($em)
);

$response = $txSignInUserService->execute(
    new SignInUserRequest(
        '[email protected]',
        'thisisnotasecretpassword'
    )
);

$newUserCreated = $response->getUser();
//...

As you can see, the use case creation and execution is the same as the non transactional, the only difference is the decoration with the Transactional Application Service.

As a collateral benefit, the Doctrine Session manages internally the flush method, so you don't need to add a flush in your Domain neither your infrastructure.

Asynchronous AMQP listeners

This library is capable to support asynchronous messaging in order to make Bounded Context capable to listen to other Bounded Context's events in an efficient way. The base for this is the both the amqp and the react's event loop. In addition, to support more efficient event loopings, we recommend the installation of one of this extensions

The usage of any of this extensions is handled by ReactPHP's event-loop in a totally transparent way.

Example

Supose we need to listen to the Acme\Billing\DomainModel\Order\OrderWasCreated event triggered via messaging from another bounded context. The following, is an example of a AMQP exchange listener that listents to the Acme\Billing\DomainModel\Order\OrderWasCreated event.

<?php

namespace Acme\Inventory\Infrastructure\Messaging\Amqp;

use stdClass;
use AMQPQueue;
use JMS\Serializer\Serializer;
use League\Tactician\CommandBus;
use React\EventLoop\LoopInterface;
use Ddd\Infrastructure\Application\Notification\AmqpExchangeListener;

class OrderWasCreatedListener extends AmqpExchangeListener
{
    private $commandBus;
    
    public function __construct(AMQPQueue $queue, LoopInterface $loop, Serializer $serializer, CommandBus $commandBus)
    {
        $this->commandBus = $commandBus;
        
        parent::construct($queue, $loop, $serializer);
    }
    
    /**
     * This method will be responsible to decide whether this listener listens to an specific
     * event type or not, given an event type name
     *
     * @param string $typeName
     *
     * @return bool
     */
    protected function listensTo($typeName)
    {
        return 'Acme\Billing\DomainModel\Order\OrderWasCreated' === $typeName;
    }

    /**
     * The action to perform
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function handle($event)
    {
        $this->commandBus->handle(new CreateOrder(
            $event->order_id,
            // ...
        ));
    }
}

And this is a possible command to create AMQP workers

<?php

namespace AppBundle\Command;

use AMQPConnection;
use AMQPChannel;
use React\EventLoop\Factory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use JMS\Serializer\Serializer;
use League\Tactician\CommandBus;

class OrderWasCreatedWorkerCommand extends Command
{
    private $serializer;
    private $commandBus;
    
    public function __construct(Serializer $serializer, CommandBus $commandBus)
    {
        $this->serializer = $serializer;
        $this->commandBus = $commandBus;
        
        parent::__construct();
    }
    
    public function execute(InputInterface $input, OutputInterface $output)
    {
        $connection = new AMQPConnection([
            'host' => 'example.host',
            'vhost' => '/',
            'port' => 5763,
            'login' => 'user',
            'password' => 'password'
        ]);
        $connection->connect();
        
        $queue = new AMQPQueue(new AMQPChannel($connection));
        $queue->setName('events');
        $queue->setFlags(AMQP_NOPARAM);
        $queue->declareQueue();
        
        $loop = Factory::create();
        
        $listener = new OrderWasCreatedListener(
            $queue,
            $loop,
            $serializer,
            $this->commandBus
        );
        
        $loop->run();
    }
}

AMQP Message producer

The intention of the AMQP message producer is to be composed in some other class. The following is an example of the usage of the AMQP message producer.

<?php

use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Ddd\Infrastructure\Application\Notification\AmqpMessageProducer;
use Ddd\Application\Notification\NotificationService;

$connection = new AMQPConnection([
    'host' => 'example.host',
    'vhost' => '/',
    'port' => 5763,
    'login' => 'user',
    'password' => 'password'
]);
$connection->connect();

$exchange = new AMQPExchange(new AMQPChannel($connection));
$exchange->setName('events');
$exchange->declare();

$config = Setup::createYAMLMetadataConfiguration([__DIR__."/src/Infrastructure/Application/Persistence/Doctrine/Config"], false);
$entityManager = EntityManager::create(['driver' => 'pdo_sqlite', 'path' => __DIR__ . '/db.sqlite'], $config);

$eventStore = $entityManager->getRepository('Ddd\Domain\Event\StoredEvent');
$publishedMessageTracker = $entityManager->getRepository('Ddd\Domain\Event\PublishedMessage');
$messageProducer = new AmqpMessageProducer($exchange);

$notificationService = new NotificationService(
    $eventStore,
    $publishedMessageTracker,
    $messageProducer
);

$notificationService->publish(/** ... **/);
Comments
  • Implementation of Specification pattern.

    Implementation of Specification pattern.

    Specification pattern by M.Fowler and E.Evans.

    Includes:

    • Single Specification.
    • Composite Specification.

    Not includes:

    • Subsumption.
    • Partially Satisfied Specification.
    opened by martinezdelariva 6
  • Replaces \DateTime with \DateTimeInterface.

    Replaces \DateTime with \DateTimeInterface.

    Today I was trying to run ddd/last-wishes and got an error regarding a StoredEvent. I noticed that the issue was already reported on the wrong repo https://github.com/dddinphp/last-wishes/issues/11

    So I decided to involve myself and try to make my first open-source contribution :)

    opened by novolog 1
  • Add a Test Scenario which helps to easily test your application services

    Add a Test Scenario which helps to easily test your application services

    Helper class which makes easy testing your Application Services in a very readable way. Source code comes inspired by Beau Siemens on his video Introduction to Event Sourcing and CQRS and Greg Young's talks about Event Sourcing below an example on how to use it:

    class MyTest extends AbstractApplicationServiceScenario
    {
        /**
         * @test
         */
        public function shouldAddReviewer()
        {
            $this->scenario
                ->given(
                    new CodeReview('a code review id', 'a title', 'a code')
                )
                ->when(
                    new RequestReviewerRequest('a reviewer id', 'a code review id')
                )
                ->then(
                    [
                        new ReviewerWasAdded('a reviewer id', 'a code review id'),
                    ]
                );
        }
    
        /**
         * @param $repository
         *
         * @return ApplicationService
         */
        public function applicationService($repository)
        {
            return new CreateCodeReviewService($repository);
        }
    
        /**
         * @return mixed Repository
         */
        public function aggregateRootRepository()
        {
            return new InMemoryCodeReviewRepository();
        }
    }
    
    
    opened by mgonzalezbaile 0
  • Fix: Use dedicated sections in Travis configuration

    Fix: Use dedicated sections in Travis configuration

    opened by localheinz 0
  • Improve tests. Add unsubscribe

    Improve tests. Add unsubscribe

    Changes:

    • Tests are now in its own space, out of the autoload.
    • Added tests for DomainEventPublisher
    • Added unsubscribe to DomainEventPublisher, which is a nice feature given the nature of singletons. As an example, without this feature is pretty easy to mess up other tests as all them share the same instance of the DomainEventPublisher.
    opened by keyvanakbary 0
  • Event's occurredOn attributes types should be immutable

    Event's occurredOn attributes types should be immutable

    First of all I want to thank you boys for your job. I bought your e-book and find it useful.

    Event's occurredOn attributes types should be immutable, but StoredEvent's occurredOn is mutable because of DateTime type. https://github.com/dddinphp/ddd/blob/master/src/Domain/Event/StoredEvent.php#L34

    Especially last-wishes app fires Event with DateTimeImmutable type - inconsistency that throws Exception. https://github.com/bystro/last-wishes/blob/master/src/Lw/Domain/Model/User/UserRegistered.php#L19

    Boys are you open for a PR? I can make some job.

    opened by bystro 0
  • PersistDomainEventSubscriber

    PersistDomainEventSubscriber

    Hi, can you suggest me how can I subscribe PersistDomainEventSubscriber globally on Symfony 3?

    For now I have registered this two services:

      event.repository:
        class: Messenger\Infrastructure\Application\Notification\DoctrineEventStoreRepository
        factory: ['@doctrine', getRepository]
        arguments: ['Messenger\Domain\Event\StoredEvent']
    
      event.publisher:
        class: Messenger\Domain\DomainEventPublisher
        factory: ['Messenger\Domain\DomainEventPublisher', instance]
    

    but I don't understand how (and where) can I subscribe my subscribers

    Thanks

    opened by matiux 0
  • Base EventSourcing classes.

    Base EventSourcing classes.

    Includes:

    • EventSourcingAggregateRoot: common functionality for aggregate roots. Inspired from last-wishes-gamify project.
    • EventSourcingStore: interface inspired from V. Vernon.
    • EventStream: class inspired from V. Vernon.
    opened by martinezdelariva 1
  • mostRecentPublishedMessageId() returns the first PublishedMessageId instead of the last

    mostRecentPublishedMessageId() returns the first PublishedMessageId instead of the last

    In the class Ddd\Infrastructure\Application\Notification\DoctrinePublishedMessageTracker on line 18 the code should be replaced with: $messageTracked = $this->findOneBy(array('typeName'=>$aTypeName), array('trackerId'=>'DESC')); in order to return the last PublishedMessageId

    opened by dadeky 2
Releases(1.4.0)
Owner
Domain-Driven Design in PHP
Domain-Driven Design in PHP
An asynchronous event driven PHP socket framework. Supports HTTP, Websocket, SSL and other custom protocols. PHP>=5.3.

Workerman What is it Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. Wo

walkor 10.2k Dec 31, 2022
Event-driven, non-blocking I/O with PHP.

Event-driven, non-blocking I/O with PHP. ReactPHP is a low-level library for event-driven programming in PHP. At its core is an event loop, on top of

ReactPHP 8.5k Dec 26, 2022
Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container, http, log and route.

Kit What is it Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container,

null 2 Sep 23, 2022
Mind is the PHP code framework designed for developers. It offers a variety of solutions for creating design patterns, applications and code frameworks.

Mind Mind is the PHP code framework designed for developers. It offers a variety of solutions for creating design patterns, applications and code fram

null 0 Dec 13, 2021
PHPneeds library (classes) package.

PHPneeds is a lightweight non-MVC PHP library for quickly start a project. About PHPneeds library (classes) package. Please use with PHPneeds base pro

PHPneeds 2 Oct 21, 2021
Provides database storage and retrieval of application settings, with a fallback to the config classes.

Provides database storage and retrieval of application settings, with a fallback to the config classes.

CodeIgniter 4 web framework 47 Dec 29, 2022
PSR Log - This repository holds all interfaces/classes/traits related to PSR-3.

PSR Log This repository holds all interfaces/classes/traits related to PSR-3. Note that this is not a logger of its own. It is merely an interface tha

PHP-FIG 10.1k Jan 3, 2023
This repository contains custom View classes for the template frameworks

Slim Views This repository contains custom View classes for the template frameworks listed below. You can use any of these custom View classes by eith

Slim Framework 308 Nov 7, 2022
A set of ready-made regex helper methods for use in your Laravel application.

Regex A set of ready-made regex helper methods for use in your Laravel application. Installation composer require hotmeteor/regex Usage Regex comes wi

Adam Campbell 229 Dec 25, 2022
Slim Framework view helper built on top of the Twig templating component

Slim Framework Twig View This is a Slim Framework view helper built on top of the Twig templating component. You can use this component to create and

Slim Framework 321 Dec 16, 2022
PHP Kafka client is used in PHP-FPM and Swoole. PHP Kafka client supports 50 APIs, which might be one that supports the most message types ever.

longlang/phpkafka Introduction English | 简体中文 PHP Kafka client is used in PHP-FPM and Swoole. The communication protocol is based on the JSON file in

Swoole Project 235 Dec 31, 2022
A multithreaded application server for PHP, written in PHP.

appserver.io, a PHP application server This is the main repository for the appserver.io project. What is appserver.io appserver.io is a multithreaded

appserver.io 951 Dec 25, 2022
Fast php framework written in c, built in php extension

Yaf - Yet Another Framework PHP framework written in c and built as a PHP extension. Requirement PHP 7.0+ (master branch)) PHP 5.2+ (php5 branch) Inst

Xinchen Hui 4.5k Dec 28, 2022
💫 Vega is a CLI mode HTTP web framework written in PHP support Swoole, WorkerMan / Vega 是一个用 PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、WorkerMan

Mix Vega 中文 | English Vega is a CLI mode HTTP web framework written in PHP support Swoole, WorkerMan Vega 是一个用 PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、Work

Mix PHP 46 Apr 28, 2022
Hello, this is simple attribute validation for PHP Models, based on the new features, presented in PHP 8

Hello, this is simple attribute validation for PHP Models, based on the new features, presented in PHP 8 It works as a standalone and can be use in custom projects or in libraries like Symfony and Laravel.

Ivan Grigorov 88 Dec 30, 2022
A PHP framework for web artisans.

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

The Laravel Framework 72k Jan 7, 2023
The Symfony PHP framework

Symfony is a PHP framework for web and console applications and a set of reusable PHP components. Symfony is used by thousands of web applications (in

Symfony 27.8k Jan 2, 2023
Open Source PHP Framework (originally from EllisLab)

What is CodeIgniter CodeIgniter is an Application Development Framework - a toolkit - for people who build web sites using PHP. Its goal is to enable

B.C. Institute of Technology 18.2k Dec 29, 2022