Your SlideShare is downloading. ×
Unbreakable Domain Models - DPC13
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Unbreakable Domain Models - DPC13

252
views

Published on

Data Mappers (like Doctrine2) help us a lot to persist data. Yet many projects are still struggling with tough questions: …

Data Mappers (like Doctrine2) help us a lot to persist data. Yet many projects are still struggling with tough questions:
- Where to put business logic?
- How to protect our code from abuse?
- Where to put queries, and how test them?

Let’s look beyond the old Gang of Four design patterns, and take some clues from tactical Domain Driven Design. At the heart of our models, we can use Value Objects and Entities, with tightly defined consistency boundaries. Repositories abstract away the persistence. Encapsulated Operations helps us to protect invariants. And if we need to manage a lot of complexity, the Specification pattern helps us express business rules in the language of the business.

These patterns help us evolve from structural data models, to rich behavioral models. They capture not just state and relationships, but true meaning.

The presentation is a fast paced introduction to some patterns and ideas that will make your Domain Model expressive, unbreakable, and beautiful.

Published in: Technology, Business

0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
252
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
0
Comments
0
Likes
3
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. UnbreakableDomain ModelsI’m Mathias Verraes.I cure complexlegacy projects.@mathiasverraeshttp://verraes.net
  • 2. DomainProblem SpaceDomain ModelSolution Space
  • 3. (Data ModelThe model’s state)
  • 4. Protect your invariants
  • 5. The domain expert says“A customer mustalways have anemail address.”* Could be different for your domain** All examples are simplified
  • 6. class CustomerTest extends PHPUnit_Framework_TestCase{/** @test */public function should_always_have_an_email(){$customer = new Customer();assertThat($customer->getEmail(),equalTo(jim@example.com));}}Test fails
  • 7. class CustomerTest extends PHPUnit_Framework_TestCase{/** @test */public function should_always_have_an_email(){$customer = new Customer();$customer->setEmail(jim@example.com);assertThat($customer->getEmail(),equalTo(jim@example.com));}}Test passes
  • 8. class CustomerTest extends PHPUnit_Framework_TestCase{/** @test */public function should_always_have_an_email(){$customer = new Customer();assertThat($customer->getEmail(),equalTo(‘jim@example.com));$customer->setEmail(‘jim@example.com’);}}Test fails
  • 9. class Customer{private $email;public function __construct($email){$this->email = $email;}public function getEmail(){return $this->email;}}Test passes
  • 10. class CustomerTest extends PHPUnit_Framework_TestCase{/** @test */public function should_always_have_an_email(){$customer = new Customer(‘jim@example.com’);assertThat($customer->getEmail(),equalTo(‘jim@example.com));}}Test passes
  • 11. Use objects asconsistency boundaries
  • 12. The domain expert meant“A customer mustalways have a validemail address.”
  • 13. $customerValidator = new CustomerValidator;if($customerValidator->isValid($customer)){// ...}
  • 14. class CustomerTest extends PHPUnit_Framework_TestCase{/** @test */public function should_always_have_a_valid_email(){$this->setExpectedException(InvalidArgumentException);new Customer(malformed@email);}}Test fails
  • 15. class Customer{public function __construct($email){if( /* ugly regex here */) {throw new InvalidArgumentException();}$this->email = $email;}}Test passes
  • 16. ViolatesSingle ResponsibilityPrinciple
  • 17. class Email{private $email;public function __construct($email){if( /* ugly regex here */) {throw new InvalidArgumentException();}$this->email = $email;}public function __toString(){return $this->email;}}Test passes
  • 18. class Customer{/** @var Email */private $email;public function __construct(Email $email){$this->email = $email;}}Test passes
  • 19. class CustomerTest extends PHPUnit_Framework_TestCase{/** @test */public function should_always_have_a_valid_email(){$this->setExpectedException(‘InvalidArgumentException’);new Customer(new Email(‘malformed@email’));}}Test passes
  • 20. Encapsulatestate and behaviorwith Value Objects
  • 21. The domain expert says“A customerorders productsand pays for them.”
  • 22. $order = new Order;$order->setCustomer($customer);$order->setProducts($products);$order->setStatus(Order::UNPAID);// ...$order->setPaidAmount(500);$order->setPaidCurrency(‘EUR’);$order->setStatus(Order::PAID);
  • 23. $order = new Order;$order->setCustomer($customer);$order->setProducts($products);$order->setStatus(new PaymentStatus(PaymentStatus::UNPAID));$order->setPaidAmount(500);$order->setPaidCurrency(‘EUR’);$order->setStatus(new PaymentStatus(PaymentStatus::PAID));
  • 24. $order = new Order;$order->setCustomer($customer);$order->setProducts($products);$order->setStatus(new PaymentStatus(PaymentStatus::UNPAID));$order->setPaidMonetary(new Money(500, new Currency(‘EUR’)));$order->setStatus(new PaymentStatus(PaymentStatus::PAID));
  • 25. $order = new Order($customer, $products);// set PaymentStatus in Order::__construct()$order->setPaidMonetary(new Money(500, new Currency(‘EUR’)));$order->setStatus(new PaymentStatus(PaymentStatus::PAID));
  • 26. $order = new Order($customer, $products);$order->pay(new Money(500, new Currency(‘EUR’)));// set PaymentStatus in Order#pay()
  • 27. Encapsulate operations
  • 28. $order = $customer->order($products);$customer->pay($order,new Money(500, new Currency(‘EUR’)));
  • 29. The domain expert says“Premium customersget special offers.”
  • 30. if($customer->isPremium()) {// send special offer}
  • 31. The domain expert says“Order 3 timesto become apremium customer.”
  • 32. interface CustomerSpecification{/** @return bool */public function isSatisfiedBy(Customer $customer);}
  • 33. class CustomerIsPremium implements CustomerSpecification{private $orderRepository;public function __construct(OrderRepository $orderRepository) {...}/** @return bool */public function isSatisfiedBy(Customer $customer){$count = $this->orderRepository->countFor($customer);return $count >= 3;}}$customerIsPremium = new CustomerIsPremium($orderRepository)if($customerIsPremium->isSatisfiedBy($customer)) {// send special offer}
  • 34. $customerIsPremium = new CustomerIsPremium;$aCustomerWith2Orders = ...$aCustomerWith3Orders = ...assertFalse($customerIsPremium->isSatisfiedBy($aCustomerWith2Orders));assertTrue($customerIsPremium->isSatisfiedBy($aCustomerWith3Orders));
  • 35. The domain expert says“Different rules applyfor different tenants.”
  • 36. interface CustomerIsPremiumextends CustomerSpecificationclass CustomerWith3OrdersIsPremiumimplements CustomerIsPremiumclass CustomerWith500EuroTotalIsPremiumimplements CustomerIsPremiumclass CustomerWhoBoughtLuxuryProductsIsPremiumimplements CustomerIsPremium...
  • 37. class SpecialOfferSender{private $customerIsPremium;public function __construct(CustomerIsPremium $customerIsPremium) {...}public function sendOffersTo(Customer $customer){if($this->customerIsPremium->isSatisfiedBy($customer)){// send offers...}}}
  • 38. <!-- if you load services_amazon.xml: --><service id="customer.is.premium"class="CustomerWith500EuroTotalIsPremium"><!-- if you load services_ebay.xml: --><service id="customer.is.premium"class="CustomerWith3OrdersIsPremium"><!-- elsewhere --><serviceid=”special.offer.sender”class=”SpecialOfferSender”><argument type=”service” id=”customer.is.premium”/></service>
  • 39. Use specifications toencapsulate rulesabout object selection
  • 40. The domain expert says“Get a list ofall premiumcustomers.”
  • 41. interface CustomerRepository{public function add(Customer $customer);public function remove(Customer $customer);/** @return Customer */public function find(CustomerId $customerId);/** @return Customer[] */public function findAll();/** @return Customer[] */public function findRegisteredIn(Year $year);}
  • 42. interface CustomerRepository{/** @return Customer[] */public function findSatisfying(CustomerSpecification $customerSpecification);}// generalized:$objects = $repository->findSatisfying($specification);
  • 43. class DbCustomerRepository implements CustomerRepository{/** @return Customer[] */public function findSatisfying(CustomerSpecification $customerSpecification){// filter Customers (see next slide)}}
  • 44. // class DbCustomerRepositorypublic function findSatisfying($specification){$foundCustomers = array();foreach($this->findAll() as $customer) {if($specification->isSatisfiedBy($customer)) {$foundCustomers[] = $customer;}}return $foundCustomers;}
  • 45. class CustomerWith3OrdersIsPremiumimplements CustomerSpecification{public function asSql() {return ‘SELECT * FROM Customer...’;}}// class DbCustomerRepositorypublic function findSatisfying($specification){return $this->db->query($specification->asSql());}
  • 46. Use double dispatchto preserve encapsulation
  • 47. $expectedCustomers = // filtered using isSatisfiedBy$actualCustomers =$repository->findSatisfying($specification);assertThat($expectedCustomers, equalTo($actualCustomers));
  • 48. Test by comparingdifferent representations
  • 49. Protect your invariantsObjects asconsistency boundariesEncapsulatestate and behavior
  • 50. More? google for:Eric EvansVaugh VernonMartin FowlerGreg YoungUdi DahanSandro MarcusoYves ReynhoutSzymon PobiegaAlberto Brandolini...
  • 51. Thanks! Questions?@mathiasverraeshttp://verraes.nethttps://joind.in/8438