+ All Categories
Home > Software > Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling the Ulabox.com monolith. From CRUD to DDD

Date post: 16-Apr-2017
Category:
Upload: aleix-verges
View: 818 times
Download: 3 times
Share this document with a friend
83
Decoupling Ulabox.com monolith From CRUD to DDD
Transcript
Page 1: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith

From CRUD to DDD

Page 2: Decoupling the Ulabox.com monolith. From CRUD to DDD

Aleix VergésBackend developer

Scrum Master

@avergess

[email protected]

Page 3: Decoupling the Ulabox.com monolith. From CRUD to DDD

1. What’s Ulabox?

Page 4: Decoupling the Ulabox.com monolith. From CRUD to DDD
Page 5: Decoupling the Ulabox.com monolith. From CRUD to DDD
Page 6: Decoupling the Ulabox.com monolith. From CRUD to DDD
Page 7: Decoupling the Ulabox.com monolith. From CRUD to DDD
Page 8: Decoupling the Ulabox.com monolith. From CRUD to DDD

2. Decoupling. Why?

Page 9: Decoupling the Ulabox.com monolith. From CRUD to DDD

/*** @param Cart $cart* @return Order $order*/public function createOrder(Cart $cart){ // Year 2011 $order = $this->moveCartToOrder($cart); $this->sendConfirmationEmail($order); $this->reindexSolr($order); $this->sendToWarehouse($order);

// Year 2012 $this->sendToFinantialErp($order); $this->sendDonationEmail($order);

// Year 2014 $this->sendToDeliveryRoutingSoftware($order);

// Year 2015 $this->sendToJustInTimeSuppliers($order);

// Year 2016 $this->sendToWarehouseSpoke($order); // WTF! $this->sendToShipLoadSoftware($order); // WTF!!!!}

Decoupling Ulabox.com monolith. From CRUD to DDD

Our problem

Page 10: Decoupling the Ulabox.com monolith. From CRUD to DDD

WTF!

Page 11: Decoupling the Ulabox.com monolith. From CRUD to DDD

Past● CRUD doesn’t make sense anymore

● It had sense at the beginning

● Product, Logistic, Delivery, Cart, Customers, ...

● It’s not sustainable.

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 12: Decoupling the Ulabox.com monolith. From CRUD to DDD

Our solution

/*** @param Cart $cart*/public function createOrder(Cart $cart){ $createOrder = new CreateOrderCommand(Cart $cart); $this->commandBus->dispatch($createOrder) }

Event bus

CreateOrder command

OrderWasCreated event

subscribe

subscribe

subscribe

subs

cribe

subscribe

subscribe

subscribesubscribe

subscribe

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 13: Decoupling the Ulabox.com monolith. From CRUD to DDD

3. The tools

Page 14: Decoupling the Ulabox.com monolith. From CRUD to DDD

● Domain

● Aggregate / Aggregate Root

● Repository

● Domain Events

● Service

● Command Bus

● Event Bus

* Domain Drive Design: https://en.wikipedia.org/wiki/Domain-driven_design

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 15: Decoupling the Ulabox.com monolith. From CRUD to DDD

4. A responsability question

Page 16: Decoupling the Ulabox.com monolith. From CRUD to DDD

Refactoring and manage technical debt is not a choice, but a responsability

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 17: Decoupling the Ulabox.com monolith. From CRUD to DDD

5. Controllers

Page 18: Decoupling the Ulabox.com monolith. From CRUD to DDD

REFUND!

Page 19: Decoupling the Ulabox.com monolith. From CRUD to DDD

5.1. OrderController5. Controllers

Page 20: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderController extends BaseController{

public function refundAction(Request $request, $id){ $em = $this->container->get('doctrine.orm.entity_manager'); $orderPayment = $em->getRepository('UlaboxCoreBundle:OrderPayment')->find($id);

$amount = $request->request->get('refund'); $data = $this->container->get('sermepa')->processRefund($orderPayment, $amount);

$orderRefund = new OrderPayment(); $orderRefund->setAmount($amount); ...

$em->persist($orderRefund); $em->flush();

return $this->redirectToRoute('order_show', ['id' => $orderPayment->getOrder()->getId()]);}

public function someOtherAction(Request $request, $id)...

}

Page 21: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith. From CRUD to DDD

Problems● Hidden dependencies

● Inheritance.

● Biz logic in the controller.

● Non aggregate root.

● Difficult to test.

Page 22: Decoupling the Ulabox.com monolith. From CRUD to DDD

* Dependency Injection: https://es.wikipedia.org/wiki/Inyecci%C3%B3n_de_dependencias

Decoupling Ulabox.com monolith. From CRUD to DDD

Solutions● Dependency Injection

● Break inheritance from base controller.

● Application services.

● Testing

Page 23: Decoupling the Ulabox.com monolith. From CRUD to DDD

5.2. Controller as a service5. Controllers

Page 24: Decoupling the Ulabox.com monolith. From CRUD to DDD

# services.yml

imports: - { resource: controllers.yml }

# controllers.yml

ulabox_ulaoffice.controllers.order: class: Ulabox\UlaofficeBundle\Controller\OrderController arguments: - '@refund' - '@router' ...

Page 25: Decoupling the Ulabox.com monolith. From CRUD to DDD

5. Controllers

5.3. Dependency Injection

Page 26: Decoupling the Ulabox.com monolith. From CRUD to DDD

/*** @Route("/orders", service="ulabox_ulaoffice.controllers.order")*/class OrderController{ /** * @param Refund $refund * @param RouterInterface $router */ public function __construct(Refund $refund, RouterInterface $router, ……..) { $this->refund = $refund; $this->router = $router; ... }}

Page 27: Decoupling the Ulabox.com monolith. From CRUD to DDD

5. Controllers

5.4. Delegate logic to services

Page 28: Decoupling the Ulabox.com monolith. From CRUD to DDD

/*** @Route("/orders", service="ulabox_ulaoffice.controllers.order")*/class OrderController{

public function refundAction(Request $request, $id){ $amount = $request->request->get('refund'); $method = $request->request->get('method'); $orderId = $request->request->get('order_id');

try { $this->refund->execute($orderId, $id, (float)$amount, $method); $this->session->getFlashBag()->add('success', 'Refund has been processed correctly'); } catch (\Exception $e) { $this->session->getFlashBag()->add('danger', $e->getMessage()); }

return new RedirectResponse($this->router->generate('order_show', ['id' => $orderId]));}

}

Page 29: Decoupling the Ulabox.com monolith. From CRUD to DDD

5. Controllers

5.5. Unit test

Page 30: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderControllerTest extends \PHPUnit_Framework_TestCase{

public function setUp(){ $this->refund = $this->prophesize(Refund::class); $this->router = $this->prophesize(RouterInterface::class); $this->orderController = new OrderController(

$this->refund->reveal(),$this->router->reveal()

);}

...}

Page 31: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderControllerTest extends \PHPUnit_Framework_TestCase{

...

public function testShouldDelegateOrderRefund(){ $orderPaymentId = 34575; $amount = 10.95; $orderId = 12345; $orderRoute = 'some/route';

$request = $this->mockRequest($orderId, $orderPaymentId, $amount, $orderRoute);

$this->refund->execute($orderId, $orderPaymentId, $amount, PaymentPlatform::REDSYS)->shouldBeCalled();

$this->router->generate('order_show', ['id' => $orderId])->willReturn($orderRoute);

$actual = $this->orderController->refundAction($request->reveal(), $orderPaymentId); $this->assertEquals(new RedirectResponse($orderRoute), $actual);}

}

Page 32: Decoupling the Ulabox.com monolith. From CRUD to DDD

6. Symfony Forms

Page 33: Decoupling the Ulabox.com monolith. From CRUD to DDD

RESCHEDULE

Page 34: Decoupling the Ulabox.com monolith. From CRUD to DDD

6. Symfony Forms

6.1. Anemic Model

Page 35: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderController extends BaseController{

public function rescheduleAction(Request $request, $id){ $order = $this->container->get('order')->reposition($id); $form = $this->createForm(new OrderType(), $order); $form->handleRequest($request);

if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($order); $em->flush();

$request->getSession()->getFlashBag()->add('success', 'Your changes were saved!');

return $this->redirect($this->generateUrl('reschedule_success')); }

return ['entity' => $entity, 'form' => $form->createView()];}

}

Page 36: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith. From CRUD to DDD

Problems● Coupling between entities and Symfony Forms.

● Anemic Model.

● Intention?

Page 37: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith. From CRUD to DDD

Solutions● Use of DTO/Command

● Reflect the Intention!

● Rich Domain.

● Testing.

Page 38: Decoupling the Ulabox.com monolith. From CRUD to DDD

6. Symfony Forms

6.2. Command !== CLI Command

Page 39: Decoupling the Ulabox.com monolith. From CRUD to DDD

class Reschedule{ public $orderId; public $addressId; public $slotVars; public $comments;

public function __construct($orderId, $addressId, $slotVars, $comments) { $this->orderId = $orderId; $this->addressId = $addressId; $this->slotVars = $slotVars; $this->comments = $comments; }}

Page 40: Decoupling the Ulabox.com monolith. From CRUD to DDD

6. Symfony Forms

6.3. Building the Form

Page 41: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderController extends BaseController{

public function rescheduleDisplayingAction(Request $request, $id){ $order = $this->orderRepository->get($id); $address = $order->deliveryAddress()->asAddress();

$rescheduleOrder = Reschedule::fromPayload([ 'order_id' => $order->getId(), 'address_id' => $address->getId(), 'slot_vars' => $order->deliverySlotVars(), 'comments' => $order->deliveryComments(), ]);

$rescheduleForm = $this->formFactory->create(OrderRescheduleType::class, $rescheduleOrder);

return ['order' => $order, 'form' => $rescheduleForm->createView()];}

}

Page 42: Decoupling the Ulabox.com monolith. From CRUD to DDD

6. Symfony Forms

6.4. Submitting the Form

Page 43: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderController extends BaseController{ public function rescheduleUpdateAction(Request $request, $id) {

$requestData = $request->get('order_reschedule');$rescheduleOrder = Reschedule::fromPayload([ 'order_id' => $id, 'address_id' => $requestData['addressId'], 'slot_vars' => $requestData['slotVars'], 'comments' => $requestData['comments'],]);

$rescheduleForm = $this->formFactory->create(OrderRescheduleType::class, $rescheduleOrder);

if ($rescheduleForm->isValid()) { $this->commandBus->dispatch($rescheduleOrder); }

return new RedirectResponse($this->router->generate($this->entity Properties['route'])); }}

Page 44: Decoupling the Ulabox.com monolith. From CRUD to DDD

6. Symfony Forms

6.5. Unit test

Page 45: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderControllerTest extends \PHPUnit_Framework_TestCase{ public function testShouldDelegateOrderRescheduleToCommandBus() {

$orderId = 12345;$addressId = 6789;$slotVars = '2016-03-25|523|2|15';$comments = 'some comments';$expectedRoute = 'http://some.return.url';

$request = $this->mockRequest($orderId, $addressId, $slotVars, $comments);$form = $this->mockForm();$form->isValid()->willReturn(true);$this->router->generate('order')->willReturn($expectedRoute);

$this->commandBus->dispatch(Argument::type(Reschedule::class))->shouldBeCalled();

$actual = $this->orderController->rescheduleUpdateAction($request->reveal(), $orderId);$this->assertEquals(new RedirectResponse($expectedRoute), $actual);

}}

Page 46: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

Page 47: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

7.1. Summing...

Page 48: Decoupling the Ulabox.com monolith. From CRUD to DDD

class OrderController extends BaseController{ public function rescheduleUpdateAction(Request $request, $id) {

$requestData = $request->get('order_reschedule');$rescheduleOrder = Reschedule::fromPayload([ 'order_id' => $id, 'address_id' => $requestData['addressId'], 'slot_vars' => $requestData['slotVars'], 'comments' => $requestData['comments'],]);

$rescheduleForm = $this->formFactory->create(OrderRescheduleType::class, $rescheduleOrder);

if ($rescheduleForm>isValid()) { $this->commandBus->dispatch($rescheduleOrder);

}

return new RedirectResponse($this->router->generate($this->entity Properties['route'])); }}

Page 49: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

7.2. Handling

Page 50: Decoupling the Ulabox.com monolith. From CRUD to DDD

class RescheduleHandler extends CommandHandler{ public function __construct( ... ) { ... }

public function handleReschedule(Reschedule $rescheduleOrder) {

$timeLineSlot = $this->slotManager->createTimelineSlotFromVars($rescheduleOrder->slotVars);$order = $this->orderRepository->get($rescheduleOrder->aggregateId);

$delivery = $order->getOrderDelivery();$delivery->setSlot($timeLineSlot->getSlot());$delivery->setLoadTime($timeLineSlot->getLoadTime());$delivery->setShift($timeLineSlot->getShift()->getShift());...

$order->rescheduleDelivery($delivery);

$this->orderRepository->save($order);$this->eventBus->publish($order->getUncommittedEvents());

}}

Page 51: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith. From CRUD to DDD

Problems● Biz logic out of domain.

● Aggregate access.

● Aggregate Root?

● Unprotected Domain.

Page 52: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith. From CRUD to DDD

Solutions● Aggregate Root. Order or Delivery?

● Unique acces point to the domain.

● Clear intention!!

● Testing.

Page 53: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

7.3. Order or Delivery?

Page 54: Decoupling the Ulabox.com monolith. From CRUD to DDD
Page 55: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

7.4. Aggregate access point

Page 56: Decoupling the Ulabox.com monolith. From CRUD to DDD

class RescheduleHandler extends CommandHandler{ public function __construct( ... ) { ... }

public function handleReschedule(Reschedule $rescheduleDelivery) { $timeLineSlot = $this->slotManager->createTimelineSlotFromVars($rescheduleDelivery->slotVars); $delivery = $this->deliveryRepository->get($rescheduleDelivery->deliveryId);

$delivery->reschedule($timeLineSlot); $this->deliveryRepository->save($delivery); $this->eventBus->publish($delivery->getUncommittedEvents()); }}

Page 57: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

7.5. Business logic

Page 58: Decoupling the Ulabox.com monolith. From CRUD to DDD

class Delivery implements AggregateRoot{ public function reschedule(TimelineSlot $timelineSlot) { $this->setDate($timelineSlot->getDate()); $this->setLoadTime($timelineSlot->getLoadTime()); $this->setSlot($timelineSlot->getSlot()); $this->setShift($timelineSlot->getShift()); $this->setLoad($timelineSlot->getLoad()); $this->setPreparation($timelineSlot->getPreparationDate());

$this->apply( new DeliveryWasRescheduled( $this->getAggregateRootId(), $this->getProgrammedDate(), $this->getTimeStart(), $this->getTimeEnd(), $this->getLoad()->spokeId() ) ); }}

Page 59: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

7.6. Unit test

Page 60: Decoupling the Ulabox.com monolith. From CRUD to DDD

class RescheduleHandlerTest extends \PHPUnit_Framework_TestCase{ public function testShouldRescheduleDelivery() { $deliveryId = 12345; $slotVars = '2016-03-25|523|2|15'; $timeLineSlot = TimelineSlotStub::random(); $delivery = $this->prophesize(Delivery::class);

$this->deliveryRepository->get($deliveryId)->willReturn($delivery); $this->slotManager->createTimelineSlotFromVars($slotVars)->willReturn($timeLineSlot);

$delivery->reschedule($timeLineSlot)->shouldBeCalled(); $this->deliveryRepository->save($delivery)->shouldBeCalled(); $this->eventBus->publish($this->expectedEvents())->shouldBeCalled();

$this->rescheduleOrderHandler->handleReschedule(new Reschedule($deliveryId, $slotVars)); }

}

Page 61: Decoupling the Ulabox.com monolith. From CRUD to DDD

class DeliveryTest extends \PHPUnit_Framework_TestCase{ public function testShouldRescheduleDelivery() { $delivery = OrderDeliveryStub::random(); $timeLineSlot = TimelineSlotStub::random();

$delivery->reschedule($timeLineSlot);

static::assertEquals($timeLineSlot->getDate(), $delivery->getProgrammedDate()); static::assertEquals($timeLineSlot->getLoadTime(), $delivery->getLoadTime()); static::assertEquals($timeLineSlot->getSlot(), $delivery->getSlot()); static::assertEquals($timeLineSlot->getShift(), $delivery->getShift()); static::assertEquals($timeLineSlot->getPreparationDate(), $delivery->getPreparation());

$messageIterator = $delivery->getUncommittedEvents()->getIterator(); $this->assertInstanceOf(

DeliveryWasRescheduled::class, $messageIterator->current()->getPayload());

} }

Page 62: Decoupling the Ulabox.com monolith. From CRUD to DDD

7. From CRUD to DDD

7.7. Domain event

Page 63: Decoupling the Ulabox.com monolith. From CRUD to DDD

DeliveryWasRescheduled

Delivery Order

Load

Slot

TimeStart

DateOrderLine

Product

Tax

Deliveries Orders

Page 64: Decoupling the Ulabox.com monolith. From CRUD to DDD

8. Aggregates and Repositories

Page 65: Decoupling the Ulabox.com monolith. From CRUD to DDD

CREDIT CARDS

Page 66: Decoupling the Ulabox.com monolith. From CRUD to DDD

8. Aggregates and Repositories

8.1. Entity / Repository

Page 67: Decoupling the Ulabox.com monolith. From CRUD to DDD

class CustomerCreditcardModel{ public function add($number, $type, $token = null, $expiryDate = null) { $customer = $this->tokenStorage->getToken()->getUser();

$creditCard = new CustomerCreditcard(); $creditCard->setNumber($number); $creditCard->setCustomer($customer); $creditCard->setType($type); $creditCard->setToken($token); $creditCard->setExpiryDate($expiryDate);

$this->creditCardRepository->add($creditCard);

return $creditCard; }}

Page 68: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith. From CRUD to DDD

Problems● Aggregate?

● CreditCardRepository???

● Unprotected Domain.

Page 69: Decoupling the Ulabox.com monolith. From CRUD to DDD

Decoupling Ulabox.com monolith. From CRUD to DDD

Solutions● Which is the Aggregate?

● What’s the Intention?

● Testing

Page 70: Decoupling the Ulabox.com monolith. From CRUD to DDD

Customer

CreditCard

class Customer implements AggregateRoot{ public function addCreditCard($number, $type, $token = '', $expiryDate = '') { $creditCard = CustomerCreditcard::create($number, $type, $token, $expiryDate); $this->creditCards->add($creditCard);

$this->apply(new CreditCardWasRegistered($this->getAggregateRootId(), $number)); }}

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 71: Decoupling the Ulabox.com monolith. From CRUD to DDD

8. Aggregates and Repositories

8.2. RegisterCreditCard

Page 72: Decoupling the Ulabox.com monolith. From CRUD to DDD

class RegisterCreditCard{ public $customerId; public $cardNumber; public $type; public $token; public $expiry;

public function __construct($customerId, $cardNumber, $type, $token, $expiry) { $this->customerId = $customerId; $this->cardNumber = $cardNumber; $this->type = $type; $this->token = $token; $this->expiry = $expiry; }}

Page 73: Decoupling the Ulabox.com monolith. From CRUD to DDD

8. Aggregates and Repositories

8.3. RegisterCreditCardHandler

Page 74: Decoupling the Ulabox.com monolith. From CRUD to DDD

class RegisterCreditCardHandler extends CommandHandler{ private $customerRepository; private $eventBus;

public function __construct( ... ) { ... }

public function handleRegisterCreditCard(RegisterCreditCard $registerCreditCard) {

$customer = $this->customerRepository->get($registerCreditCard->customerId())

$customer->addCreditCard( $registerCreditCard->cardNumber(), $registerCreditCard->type(), $registerCreditCard->token(), $registerCreditCard->expiry()

);

$this->customerRepository->save($customer);$this->eventBus->publish($customer->getUncommittedEvents());

}}

Page 75: Decoupling the Ulabox.com monolith. From CRUD to DDD

8. Aggregates and Repositories

8.4. Business rules

Page 76: Decoupling the Ulabox.com monolith. From CRUD to DDD

class Customer implements AggregateRoot{ public function addCreditCard($number, $type, $token, $expiryDate) {

if ($this->creditCardExists($number, $type)) { $this->renewCreditCard($number, $type, $token, $expiryDate); return;

}

$creditCard = CustomerCreditcard::create($number, $type, $token, $expiryDate); $this->creditCards->add($creditCard);

$this->apply(new CreditCardWasRegistered($this->getAggregateRootId(), $number)); }

private function renewCreditCard($number, $type, $token, $expiryDate) { ... }}

Page 77: Decoupling the Ulabox.com monolith. From CRUD to DDD

9. Learned lessons

Page 78: Decoupling the Ulabox.com monolith. From CRUD to DDD

This is not a Big-Bang

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 79: Decoupling the Ulabox.com monolith. From CRUD to DDD

Aggregate Election

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 80: Decoupling the Ulabox.com monolith. From CRUD to DDD

Communication

Decoupling Ulabox.com monolith. From CRUD to DDD

Page 81: Decoupling the Ulabox.com monolith. From CRUD to DDD

TeamDecoupling Ulabox.com monolith. From CRUD to DDD

Page 82: Decoupling the Ulabox.com monolith. From CRUD to DDD

¡¡¡Be a Professional!!!Decoupling Ulabox.com monolith. From CRUD to DDD


Recommended