DDD with Broadway and the Design Pattern State

Par @jbnahan69
Attention, cet article est ancien. Les informations ou instructions qu'il contient peuvent ne plus fonctionner.

Note : This article is the translate of this article. I use Google Translate for help me to write in english. Please, if you read wrong phrase send me the correct by one comment.

In a refactoring sprint, I found that my main aggregate class took much overweight.

I had exceeded 750 lines of code with, in many actions, a "switch" or a dozen "IF". This did not please me very much because, if a change was required with this level, the amendment would be difficult.

Taking a step back on my code (it is always a good idea to step back and re-read his code to someone else) I realized that my class had defined constants 5 and a property "state "used in all the" switch "or" if ". A little research and rereading the book "Design Pattern - Head First", I fell on the design pattern "Status".

On reflection and looking at the code that implements this pattern, I decided to implement my aggregate.

Thus begins an intense reflection!

Since the methods related to the state are redirected directly to the object "state" current, how to make the events from the object "state"? How to change my state in aggregate? How not to expose the `setState () method to prevent an` external element does not change the state of the aggregate?

All these issues have a simple answer. But for that, let's take them one by one.
How to send events from the "state" object?
By storing it in an instance variable of type "table." The aggregate will recover after the execution of the action to issue them.

How to change the state in my remote?
It does not change location ... It's always in the protected methods apply ... `()`. They are the only empowered to modify the internal state of the aggregate.

How not to expose the `setState () method to prevent an` external element does not change the state of the aggregate?
The previous answer there also answers! There is no `setState ()` function. The "state" objects are instantiated and assigned directly to the events of the application methods.

In the end, the two design patterns are perfectly compatible and produce a very effective composition.

Adding a condition is now possible by the application of the "State pattern." Now my class aggregate has lost more than 250 lines of code and all "if" and "switch" disappeared.

The implementation with Broadway :

<?php

interface State
{
    public function change();

    public function getEvents();
}

abstract class BaseState implements State
{
    private $events;

    private $aggregate;

    /**
     * The aggregate link is for acces to property of aggegate.
     */
    public function __construct($aggregate)
    {
        $this->aggregate = $aggregate;
    }

    /**
     * return all events not sent
     */
    public function getEvents()
    {
        $events = $this->events;

        $this->events = [];

        return $events;
    }

    private function addEvent($event)
    {
        $this->events[] = $event;
    }
}

class FirstState extends BaseState
{
    public function change()
    {
        // Store event into state object
        $this->addEvent(new ChangeEvent($this->aggregate->getAggregateRootId()));
    }
}

class SecondState extends BaseState
{
    public function change()
    {
        //No change
        throw new \Exception("Unable to change the state !");
    }
}

Agregate and Event :

<?php

use Broadway\EventSourcing\EventSourcedAggregateRoot;
use FirstState;
use SecondState;

class Agregat extends EventSourcedAggregateRoot
{
    private $id;

    private $state;

    public function getAggregateRootId()
    {
        return $this->id;
    }


    public static function make($id)
    {
        //...
        $this->apply(new MakeEvent($this->getAggregateRootId()));
    }

    protected function applyMakeEvent(MakeEvent $event)
    {
        $this->id = $event->getId();
        $this->state = new FirstState($this);
    }

    public function change()
    {
        $this->state->change();
        $this->applyMany($this->state->getEvents());
    }

    protected function applyChange(ChangeEvent $event)
    {
        $this->state = new SecondState($this);
    }

    private function applyMany(array $events)
    {
        foreach ($events as $event) {
            $this->apply($event);
        }
    }

}

class ChangeEvent {
    private $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

    public function getId()
    {
        return $this->id;
    }
}

class MakeEvent {
    private $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

    public function getId()
    {
        return $this->id;
    }
}

This branch on my test-broadway repository apply this pattern. Write a comment if you need help or if you want speak...

Read also :

 

Author avatar
Jean-Baptiste Nahan

Consultant Expert Web, j'aide les entreprises ayant des difficultés avec leur projet Web (PHP, Symfony, Sylius).

@jbnahan69 | Macintoshplus | Linkedin | JB Dev Labs