Skip to content

Latest commit

 

History

History
213 lines (171 loc) · 5.64 KB

domain-model.md

File metadata and controls

213 lines (171 loc) · 5.64 KB

Domain model

Behavior of model

Feature: Basket

  Scenario: Pick up basket
    Given I don't have basket
     When I pick up basket
     Then I should be notified that was picked up
      And My basket should contain 0 products

  Scenario: Add product to basket
    Given I have basket picked up
     When I add product with id "6a45032e-738a-48b7-893d-ebdc60d0c3b7" and name "Teapot" to my basket
     Then I should be notified that product was added to basket
      And My basket should contain 1 products

  Scenario: Remove product from basket
    Given I have basket picked up
      And My basket contains products:
        | productId                            | name   |
        | 6a45032e-738a-48b7-893d-ebdc60d0c3b7 | Teapot |
     When I remove product with id "6a45032e-738a-48b7-893d-ebdc60d0c3b7" from my basket
     Then I should be notified that product was removed from basket
      And My basket should contain 0 products

  Scenario: Remove not existing product from basket
    Given I have basket picked up
     When I remove product with id "6a45032e-738a-48b7-893d-ebdc60d0c3b7" from my basket
     Then I should be notified that product does not exists
      And My basket should contain 0 products

Aggregate

<?php

namespace EsSandbox\Basket\Model;

use EsSandbox\Common\Model\AggregateHistory;
use EsSandbox\Common\Model\Event;
use EsSandbox\Common\Model\EventSourcedAggregate;
use EsSandbox\Common\Model\UnableToReconstitute;
use Ramsey\Uuid\UuidInterface;

final class Basket implements EventSourcedAggregate
{
    /** @var UuidInterface */
    private $basketId;

    /** @var Event[] */
    private $uncommittedEvents = [];

    private $products = [];

    private function __construct()
    {
    }

    /**
     * @param UuidInterface $basketId
     *
     * @return Basket
     */
    public static function pickUp(UuidInterface $basketId)
    {
        $self  = new self();
        $event = new BasketWasPickedUp($basketId);

        $self->applyBasketWasPickedUp($event);

        $self->recordThat($event);

        return $self;
    }

    /**
     * @param UuidInterface $productId
     * @param $name
     */
    public function addProduct(UuidInterface $productId, $name)
    {
        $event = new ProductWasAddedToBasket($this->id(), $productId, $name);

        $this->applyProductWasAddedToBasket($event);

        $this->recordThat($event);
    }

    /**
     * @param UuidInterface $productId
     */
    public function removeProduct(UuidInterface $productId)
    {
        if (!$this->hasProduct($productId)) {
            throw new ProductDoesNotExist(
                sprintf(
                    'Product with id %s does not exist within basket with id %s',
                    $productId,
                    $this->basketId
                )
            );
        }

        $event = new ProductWasRemovedFromBasket($this->id(), $productId);

        $this->applyProductWasRemovedFromBasket($event);

        $this->recordThat($event);
    }

    /**
     * @param AggregateHistory $history
     *
     * @return Basket
     */
    public static function reconstituteFrom(AggregateHistory $history)
    {
        $self = new self();

        $self->validateHistory($history);

        foreach ($history as $event) {
            $reflection = new \ReflectionClass($event);
            $method     = 'apply'.$reflection->getShortName();

            $self->$method($event);
        }

        return $self;
    }

    /** {@inheritdoc} */
    public function recordThat(Event $event)
    {
        $this->uncommittedEvents[] = $event;
    }

    /** {@inheritdoc} */
    public function id()
    {
        return $this->basketId;
    }

    /** {@inheritdoc} */
    public function uncommittedEvents()
    {
        $uncommittedEvents = $this->uncommittedEvents;

        $this->uncommittedEvents = [];

        return $uncommittedEvents;
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->products);
    }

    /** @SuppressWarnings(PHPMD.UnusedPrivateMethod) */
    private function applyBasketWasPickedUp(BasketWasPickedUp $event)
    {
        $this->basketId = $event->id();
    }

    private function applyProductWasAddedToBasket(ProductWasAddedToBasket $event)
    {
        $this->products[(string) $event->productId()] = $event->name();
    }

    private function applyProductWasRemovedFromBasket(ProductWasRemovedFromBasket $event)
    {
        if ($this->hasProduct($productId = $event->productId())) {
            unset($this->products[(string) $productId]);
        }
    }

    private function hasProduct(UuidInterface $productId)
    {
        return isset($this->products[(string) $productId]);
    }

    /** @SuppressWarnings(PHPMD.UnusedPrivateMethod) */
    private function validateHistory(AggregateHistory $history)
    {
        if (0 === $history->count()) {
            throw new UnableToReconstitute('Unable to reconstitute Basket aggregate without any events.');
        }

        if (!$history[0] instanceof BasketWasPickedUp) {
            throw new UnableToReconstitute(
                sprintf('Unable to reconstitute Basket aggregate without initial "%s" event.', BasketWasPickedUp::class)
            );
        }
    }
}

Domain events

Event type Payload
BasketWasPickedUp basketId
ProductWasAddedToBasket basketId, productId, name
ProductWasRemovedFromBasket basketId, productId

Commands of CommandBus

  • PickUpBasket
  • AddProductToBasket
  • RemoveProductFromBasket