$30 off During Our Annual Pro Sale. View Details »

Implementing DDD in your Symfony project

Implementing DDD in your Symfony project

This presentation shows a simple approach of a DDD implementation in a Symfony project.
Was exposed in deSymfonyDay 2014 (Barcelona)

Sergio Moya

May 31, 2014
Tweet

More Decks by Sergio Moya

Other Decks in Technology

Transcript

  1. Sergio Moya @soyelsergillo
    * $* *!# %
    !%
    $* **

    View Slide

  2. $ *$# %& $ *
    Backend Engineer at Social Point
    !
    DDD Fan
    !
    Hip Hop Culture lover
    !
    Badaloní (from Badalona)
    $# *!
    (
    ((($# *$

    View Slide

  3. (%(($
    An example of a Business requested feature
    based on a TRUE STORY
    Project file distribution
    The Domain Model
    Use of Domain Events
    Adapter for our component
    !
    Integrating in our Symfony Project

    View Slide






  4. %)%
    &"&% &$
    &
    )!#%
    &
    %)%
    * &##


    View Slide

  5. &"&% &$
    &
    &

    %)%

    A sphere of knowledge,
    influence, or activity. The
    subject area to which the
    user applies a program is
    the domain of the
    software.
    A description of a
    boundary (typically a
    subsystem, or the work of
    a particular team) within
    which a particular model
    is defined and applicable.
    A language structured
    around the domain model
    and used by all team
    members within a
    bounded context to
    connect all the activities
    of the team with the
    software.
    %$$

    View Slide

  6. KNOW YOUR
    UBIQUITOUS LANGUAGE

    View Slide

  7. The Lead of the Backend engineers team has decided to split the team into smaller
    two-member teams.
    !
    Each team will develop a different component.
    !
    Our team will develop the Payment Component

    View Slide

  8. View Slide

  9. %#"&#%$
    THERE IS NO BASKET OR CART
    !
    ONE PRODUCT, ONE ORDER
    !
    A PRODUCT WILL HAVE CONDITIONS TO BE PURCHASED
    !
    THE SYSTEM SHOULD TRACK THE ORDERS

    View Slide

  10. &$% #
    ##

    !# &%
    *

    View Slide

  11. View Slide

  12. #%%&

    View Slide

  13. #%%%$

    View Slide

  14. %%%* ##
    /**!
    * CustomerOrder!
    *!
    * @ORM\Table()!
    * @ORM\Entity(repositoryClass="Tato\Bundle\PaymentBundle\Entity
    \CustomerOrderRepository")!
    */!
    class CustomerOrder!
    {!
    /**!
    * Set product!
    *!
    * @param \stdClass $product!
    * @return CustomerOrder!
    */!
    public function setProduct($product)!
    {!
    $this->product = $product;!
    !
    return $this;!
    }!
    . . .!
    }!
    !

    View Slide

  15. %%%*&$% #
    /**!
    * Customer!
    *!
    * @ORM\Table()!
    * @ORM\Entity(repositoryClass="Tato\Bundle\PaymentBundle\Entity
    \CustomerRepository")!
    */!
    class Customer!
    {!
    /**!
    * @var integer!
    *!
    * @ORM\Column(name="id", type="integer")!
    * @ORM\Id!
    * @ORM\GeneratedValue(strategy="AUTO")!
    */!
    private $id;!
    !
    . . .!
    }!
    !

    View Slide

  16. %#

    View Slide

  17. % ###
    public function buyProduct(Product $product, Customer $customer, Money $moneyPayed)!
    {!
    $productPrice = $product->getPrice();!
    if ($moneyPayed->getCurrency() !== $productPrice->getCurrency()) {!
    throw new OrderException('The Currency of that payment is invalid');!
    }!
    !
    if ($moneyPayed->getPrice() !== $productPrice->getPrice()) {!
    throw new OrderException('You must pay me!');!
    }!
    !
    $conditions = $product->getConditions();!
    !
    foreach ($conditions as $condition) {!
    if (!$condition->isValid($customer)) {!
    throw new OrderException('You must meet the product requirements!');!
    }!
    }!
    !
    $goods = $product->getGoods();!
    foreach ($goods as $good) {!
    $good->give($customer);!
    }!
    }!

    View Slide

  18. $#'

    %#
    #
    #! $%
    #*
    #
    $$ (
    $#'

    View Slide

  19. THIS IS WAR!

    View Slide

  20. BUT YOU WANT THIS

    View Slide

  21. BUT WE WANT THIS
    TODAY
    YOU WILL GET THIS

    View Slide

  22. %-$ #%$* *

    View Slide

  23. %-$ #%$* *
    # %

    View Slide

  24. ,
    $#
    ,
    ,
    !*%&
    ,
    ! %
    !*%
    &
    $%#&%

    View Slide

  25. &$% #
    A CUSTOMER CAN ORDER A PRODUCT
    !
    A CUSTOMER CAN PAY AN ORDER

    View Slide

  26. &$% #
    public function order(Product $product)!
    {!
    $order = new Order(rand(), $this, $product);!
    $this->recordThat(!
    new OrderCreated($order->getId(), $this->id, $product)!
    );!
    !
    return $order;!
    }!
    We are recording
    Events!

    View Slide

  27. &$% #
    public function pay(Order $order, Money $money)!
    {!
    $status = $order->pay($money);!
    $this->recordThat(new OrderPaid($this->id, $order->getId(), $money));!
    if ($status === OrderStatus::ORDER_FAILED) {!
    $this->recordThat(new OrderFailed($order->getId(), $this->id));!
    }!
    if ($status === OrderStatus::ORDER_PAID) {!
    $this->recordThat(!
    new OrderPaid($order->getId(), $this->id, $money)!
    );!
    }!
    return $status;!
    }

    View Slide

  28. ##
    AN ORDER MUST ENSURE THAT THEIR
    PRODUCT MEETS THEIR CONDITIONS
    !
    AN ORDER CAN BE PAID

    View Slide

  29. ##
    public function __construct($id, Customer $customer, $product)!
    {!
    $this->id = $id;!
    $this->customer = $customer;!
    $this->status = new OrderStatus(OrderStatus::ORDER_STARTED);!
    $this->guardProductConditions($product);!
    $this->product = $product;!
    }
    Ensure that the product
    satisfies business conditions

    View Slide

  30. public function pay(Money $money)!
    {!
    try {!
    if (!$this->product->getPrice()->isEqualTo($money)) {!
    throw new OrderException('The Order amount is not satisfied');!
    }!
    $this->product->deliver($this->customer);!
    $this->status = OrderStatus::create(OrderStatus::ORDER_PAID);!
    } catch (\Exception $e) {!
    $this->status = OrderStatus::create(OrderStatus::ORDER_FAILED);!
    }!
    return $this->status;!
    }
    ##
    Return what we need

    View Slide

  31. (% &%#! $% #$

    View Slide

  32. (% &%#! $% #$
    $(%

    View Slide

  33. interface OrderRepository!
    {!
    public function orderExists($orderId);!
    !
    public function find($id, $customerId);!
    !
    public function findByCustomerId($customerId);!
    !
    public function save($id, $productId, $customerId, $status = 1);!
    !
    public function remove($id);!
    !
    public function removeByCustomerId($customerId);!
    !
    public function clearAll();!
    }!
    ##
    We provide an Interface

    View Slide

  34. &%(#

    View Slide

  35. &%(#
    % ###

    View Slide

  36. View Slide

  37. public function buyProductAction(Request $request)!
    {!
    $customer = new Customer($request->request->get('customerId'));!
    !
    $productRepository = $this->get('tato.payment.product_respository');!
    $product = $productRepository->find($request->request->get('productId'));!
    !
    $order = $customer->order($product);!
    !
    $paid = new Money(!
    $request->request->get('paid'), !
    $request->request->get(‘currency')!
    );!
    $status = $order->pay($paid);!
    !
    $this->get(‘tato.payment.order_repository')->save(!
    ! ! $order->getId(), !
    $product, !
    $customer->getId(), !
    $status->getStatus()!
    );!
    !
    return new JsonResponse(array('status' => $status->getStatus()));!
    }!
    }
    $* * %# #

    View Slide

  38. %-$ #%$* *

    View Slide

  39. TAKE A PIECE OF
    CQRS

    View Slide

  40. public function handle(BuyProductCommand $command)!
    {!
    $customer = new Customer($command->customerId);!
    $product = $this->productRepository->find($command->productId);!
    !
    $order = $customer->order($product);!
    $status = $order->pay(new Money($command->paid, $command->currency));!
    !
    $events = $customer->getRecordedEvents();!
    !
    // @todo do something with this events!
    !
    $this->orderRepository->setOrder(!
    $command->gatewayName,!
    $order->getId(),!
    $command->customerId,!
    $status->getStatus()!
    );!
    !
    return $order;!
    }
    #
    We have Events!

    View Slide

  41. class BuyProductCommand!
    {!
    public $customerId;!
    !
    public $productId;!
    !
    public $paid;!
    !
    public $currency;!
    !
    public function __construct($customerId, $productId, $paid, $currency)!
    {!
    $this->customerId = $customerId;!
    $this->productId = $productId;!
    $this->paid = $paid;!
    $this->currency = $currency;!
    } !
    }
    %
    A simple DTO

    View Slide

  42. ! %

    View Slide

  43. ('$
    An example of a Business requested feature
    based on a TRUE STORY
    Project file distribution
    The Domain Model
    Use of Domain Events
    Adapter for our component
    !
    Integrating in our Symfony Project

    View Slide

  44. %%('
    ! %

    View Slide

  45. $%#&%

    $#
    ,
    ,
    !*%&
    ,
    ! %
    !*%
    &

    View Slide

  46. (!&%$$$ #
    %#!%% $

    View Slide

  47. ###! $% #*
    !
    class CustomOrderRepository extends EntityRepository implements
    OrderRepository!
    {!
    . . .!
    !
    /**!
    * Checks if an order already exists.!
    *!
    * @param int $orderId!
    * @return bool!
    * @throws \Tato\Component\Payment\Exception\Order
    \OrderRepositoryException!
    */!
    public function orderExists($orderId)!
    {!
    ! ! ! $order = $this->entityManager->find('Order:Order', $orderId);!
    !
    return $order instanceof Order;!
    }!
    !
    . . . !
    }
    DOCTRINE!

    View Slide

  48. $#'$
    services:!
    tato.payment.order_repository:!
    class: Tato\Component\Game\CustomOrderRepository!
    arguments:!
    - @doctrine.orm.entity_manager!
    !
    tato.payment.product_repository:!
    class: Tato\Component\Game\Product\CustomProductRepository!
    arguments:!
    - @doctrine.orm.entity_manager!
    !
    tato.payment.command.handler.buy_product:!
    class: Tato\Component\Payment\Command\BuyProductCommandHandler!
    arguments:!
    order_repository: @tato.payment.order_repository!
    product_repository: @tato.payment.product_repository!

    View Slide

  49. %#!!
    doctrine:!
    orm:!
    auto_generate_proxy_classes: "%kernel.debug%"!
    auto_mapping: false!
    mappings:!
    Order:!
    type: yml!
    is_bundle: false!
    dir: %kernel.root_dir%/../src/Tato/Bundle/PaymentBundle/
    Resources/config/doctrine!
    prefix: Tato\Component\Payment\Order!
    alias: PaymentOrder
    %%!+$& (% $% # %#%%$ &%$ $* *&

    View Slide

  50. %$$%!

    View Slide

  51. %$$%!
    %%#*! %

    View Slide

  52. !*% %# #
    public function buyProductAction(Request $request)!
    {!
    $customerId = $request->request->get('customerId');!
    $productId = $request->request->get('productId');!
    $paid = $request->request->get('paid');!
    $currency = $request->request->get('currency');!
    $command = new BuyProductCommand(!
    $customerId, !
    $productId, !
    $paid, !
    $currency!
    );!
    $commandHandler = $this->get('tato.payment.command.handler.buy_product');!
    $order = $commandHandler->handle($command);!
    $events = $order->getRecordedEvents();!
    // @todo do something wit this events!
    return new JsonResponse(array('status' => $order->getStatus()));!
    }

    View Slide

  53. public function buyProductAction(Request $request)!
    {!
    $customer = new Customer($request->request->get('customerId'));!
    !
    $productRepository = $this->get('tato.payment.product_respository');!
    $product = $productRepository->find($request->request->get('productId'));!
    !
    $order = $customer->order($product);!
    !
    $paid = new Money(!
    $request->request->get('paid'), !
    $request->request->get(‘currency')!
    );!
    $status = $order->pay($paid);!
    !
    $this->get(‘tato.payment.order_repository')->save(!
    ! ! $order->getId(), !
    $product, !
    $customer->getId(), !
    $status->getStatus()!
    );!
    !
    return new JsonResponse(array('status' => $status->getStatus()));!
    }!
    }
    %#$%!!#
    Business Specification
    in a Controller
    Coupled to Symfony DIC

    View Slide

  54. #%%



    REPOSITORIES
    DIC
    &
    ADAPTERS

    *%$
    #( #

    View Slide

  55. BE HEXAGONAL!

    View Slide

  56. ENJOY DDD

    View Slide

  57. $ %#$%
    http://buttercup-php.github.io/protects
    http://vaughnvernon.co
    http://dddinphp.org
    http://domainlanguage.com/ddd/patterns/DDD_Reference_2011-01-31.pdf
    http://ebookbrowsee.net/domain-driven-design-step-by-step-pdf-d322892202
    http://martinfowler.com/bliki/CQRS.html
    http://www.amazon.es/Domain-Driven-Design-Tackling-Complexity-Software/dp/
    0321125215
    http://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/
    dp/0321834577

    View Slide

  58. "&$% $

    View Slide

  59. Sergio Moya @soyelsergillo
    %$
    -

    View Slide