Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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)

0d83f514dfdd88bd9315481cee61fda8?s=128

Sergio Moya

May 31, 2014
Tweet

Transcript

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

    
  2. $ *$# %& $ * Backend Engineer at Social Point

    ! DDD Fan ! Hip Hop Culture lover ! Badaloní (from Badalona) $#  *! (  ((($#  *$
  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
  4.      %)% &"&% &$ & 

    )!#%  &  %)%  * &## 
  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. %$$
  6. KNOW YOUR UBIQUITOUS LANGUAGE

  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
  8. None
  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
  10. &$% # ## &#  !# &%  *

  11. None
  12. #%%&

  13. #%%%$

  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;! }! . . .! }! !
  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;! ! . . .! }! !
  16. %#

  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);! }! }!
  18. $#'   %# # #! $% #*  #

    $$ ( $#'
  19. THIS IS WAR!

  20. BUT YOU WANT THIS

  21. BUT WE WANT THIS TODAY YOU WILL GET THIS

  22. %-$ #%$* *

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

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

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

    A CUSTOMER CAN PAY AN ORDER
  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!
  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;! }
  28. &#  ## AN ORDER MUST ENSURE THAT THEIR PRODUCT

    MEETS THEIR CONDITIONS ! AN ORDER CAN BE PAID
  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
  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
  31. (% &%#! $% #$

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

  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
  34. &%(#

  35. &%(# % ###

  36. None
  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()));! }! } $* * %# #
  38. %-$ #%$* * 

  39. TAKE A PIECE OF CQRS

  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!
  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
  42.  ! %  

  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
  44. %%('  ! %

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

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

  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!
  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!
  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 %%!+$& (% $% # %#%%$ &%$ $* *&
  50. %$$%!

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

  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()));! }
  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
  54. &##%%&#      REPOSITORIES DIC & ADAPTERS

      *%$ #( #
  55. BE HEXAGONAL!

  56. ENJOY DDD

  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
  58. "&$% $

  59. Sergio Moya @soyelsergillo %$ -