SlideShare una empresa de Scribd logo
1 de 59
Descargar para leer sin conexión
Unbreakable
Domain Models
@mathiasverraes
A Map of the World
London
3h train rides

Kortrijk, Belgium

Paris

Amsterdam
All models are wrong,
but some are useful.
I’m Mathias Verraes
I'm an independent
consultant.
I help teams build
enterprise web
applications.
Blog
verraes.net
!

Podcast with @everzet
elephantintheroom.io
!

DDD in PHP
bit.ly/dddinphp
Domain
Problem Space
Domain Model
Solution Space
Data Model ~= Structural Model ~= State
!

Domain Model ~= Behavioral Model
!
Protect your invariants
The domain expert says

“A customer must
always have an
email address.”
* Could be different for your domain
** All examples are simplified
Test fails

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 passes

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 fails

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’);!
!

}!
}
final class Customer!
{!
private $email;!
!

public function __construct($email)!
{!
$this->email = $email;!
}!
!

public function getEmail()!
{!
return $this->email;!
}!
}
Test passes

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') !
);!
}!
}
Use objects as
consistency boundaries
final class ProspectiveCustomer !
{!
public function __construct()!
{!
// no email!
}!
}!
!

final class PayingCustomer !
{ !
public function __construct($email)!
{!
$this->email = $email;!
}!
}
Make the implicit
explicit
final class ProspectiveCustomer !
{!
/** @return PayingCustomer */!
public function convertToPayingCustomer($email)!
{ !
//...!
}!
}!
!

final class PayingCustomer !
{ !
//...!
}
The domain expert meant

“A customer must
always have a valid
email address.”
$customerValidator = new CustomerValidator;!
if($customerValidator->isValid($customer)){!
// ...!
}
Test fails

class CustomerTest extends PHPUnit_Framework_TestCase!
{!
/** @test */!
public function should_always_have_a_valid_email()!
{!
!

$this->setExpectedException(!
'InvalidArgumentException'!
);!
!

new Customer('malformed@email');!
!

}!
}
Test passes

final class Customer !
{!
public function __construct($email)!
{!
if( /* boring validation stuff */) {!
throw new InvalidArgumentException();!
}!
$this->email = $email;!
}!
}
Violates
Single Responsibility
Principle
Test passes

final class Email!
{!
private $email;!
!

public function __construct($email)!
{!
if( /* boring validation stuff */) {!
throw new InvalidArgumentException();!
}!
$this->email = $email;!
}!
!

public function __toString() !
{!
return $this->email;!
} !
}
Test passes

final class Customer!
{!
/** @var Email */!
private $email;!
!

public function __construct(Email $email)!
{!
$this->email = $email;!
}!
}
Test passes

class CustomerTest extends PHPUnit_Framework_TestCase!
{!
/** @test */!
public function should_always_have_a_valid_email()!
{!
!

$this->setExpectedException(!
‘InvalidArgumentException’!
);!
!

new Customer(new Email(‘malformed@email’));!
!

}!
}
Entity
!

Equality by
Identity
Lifecycle
Mutable

Value
Object
Equality by
Value
!

Immutable
Encapsulate
state and behavior
with Value Objects
The domain expert says

“A customer
orders products
and pays for them.”
$order = new Order;!
$order->setCustomer($customer);!
$order->setProducts($products);!
$order->setStatus(Order::UNPAID);!
!
!

// ...!
!
!

$order->setPaidAmount(500);!
$order->setPaidCurrency(‘EUR’);!
!

$order->setStatus(Order::PAID);!
!
$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)!
);
$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)!
);
$order = new Order($customer, $products);!
// set PaymentStatus in Order::__construct()!
!
!
!
!
!
!
!

$order->setPaidMonetary(!
new Money(500, new Currency(‘EUR’))!
);!
$order->setStatus(!
new PaymentStatus(PaymentStatus::PAID)!
);
$order = new Order($customer, $products);!
!
!
!
!
!
!
!
!

$order->pay(!
new Money(500, new Currency(‘EUR’))!
);!
// set PaymentStatus in Order#pay()!
!
Encapsulate operations
$order = $customer->order($products);!
!
!
!
!
!
!
!
!

$customer->payFor(!
$order,!
new Money(500, new Currency(‘EUR’))!
);!
!
The domain expert says

“Premium customers
get special offers.”
if($customer->isPremium()) {!
// send special offer!
}
The domain expert says

“Order 3 times
to become a
premium customer.”
interface CustomerSpecification !
{!
/** @return bool */!
public function isSatisfiedBy(Customer $customer); !
}
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!
}!
$customerIsPremium = new CustomerIsPremium;!
!

$aCustomerWith2Orders = ...!
$aCustomerWith3Orders = ...!
!

assertFalse(!
$customerIsPremium->isSatisfiedBy($aCustomerWith2Orders)!
);!
!

assertTrue(!
$customerIsPremium->isSatisfiedBy($aCustomerWith3Orders)!
);!
!
!
The domain expert says

“Different rules apply
for different tenants.”
interface CustomerIsPremium !
extends CustomerSpecification!
!

final class CustomerWith3OrdersIsPremium !
implements CustomerIsPremium!
!

final class CustomerWith500EuroTotalIsPremium!
implements CustomerIsPremium!
!

final class CustomerWhoBoughtLuxuryProductsIsPremium!
implements CustomerIsPremium!
!

...!
final class SpecialOfferSender!
{!
private $customerIsPremium;!
!
!

public function __construct(!
CustomerIsPremium $customerIsPremium) {...}!
!
!

public function sendOffersTo(Customer $customer) !
{!
if($this->customerIsPremium->isSatisfiedBy(!
$customer!
)) !
{!
// send offers...!
}!
}!
}!
!

<!-- 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 -->!
<service !
id=”special.offer.sender”!
class=”SpecialOfferSender”>!
<argument type=”service” id=”customer.is.premium”/>!
</service>
Use specifications to
encapsulate rules
about object selection
The domain expert says

“Get a list of
all premium
customers.”
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);!
}!
Use repositories to
create the illusion of
in-memory collections
interface CustomerRepository!
{!
!

/** @return Customer[] */!
public function findSatisfying(!
CustomerSpecification $customerSpecification!
);!
!

}!
!
!

// generalized:!
$objects = $repository->findSatisfying($specification);!
class DbCustomerRepository implements CustomerRepository!
{!
/** @return Customer[] */!
public function findSatisfying(!
CustomerSpecification $specification) !
{!
!

return array_filter(!
$this->findAll(),!
function(Customer $customer) use($specification) {!
return $specification->isSatisfiedBy($customer);!
}
!
);!
!

}!
}!
final class CustomerWith3OrdersIsPremium!
implements CustomerSpecification!
{!
public function asSql() {!
return ‘SELECT * FROM Customer...’;!
}!
}!
!
!

// class DbCustomerRepository !
public function findSatisfying($specification) !
{!
return $this->db->query($specification->asSql());
}

!
Use double dispatch
to preserve encapsulation
$expectedCustomers = array_filter(!
$repository->findAll(),!
// filter…!
);!
!

$actualCustomers = !
$repository->findSatisfying($specification);!
!

assertThat($expectedCustomers, equalTo($actualCustomers));
Test by comparing
different representations
of the same rule
Protect your invariants
!

Objects as
consistency boundaries
!

Encapsulate
state and behavior
Thanks! Questions?
I ♥ Feedback	

joind.in/10690
!

Blog, Slides, other talks:	

verraes.net	

@mathiasverraes

Más contenido relacionado

La actualidad más candente

A Z Introduction To Ruby On Rails
A Z Introduction To Ruby On RailsA Z Introduction To Ruby On Rails
A Z Introduction To Ruby On Rails
railsconf
 
Advisor Jumpstart: JavaScript
Advisor Jumpstart: JavaScriptAdvisor Jumpstart: JavaScript
Advisor Jumpstart: JavaScript
dominion
 

La actualidad más candente (19)

Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
A Z Introduction To Ruby On Rails
A Z Introduction To Ruby On RailsA Z Introduction To Ruby On Rails
A Z Introduction To Ruby On Rails
 
A-Z Intro To Rails
A-Z Intro To RailsA-Z Intro To Rails
A-Z Intro To Rails
 
Best practices for crafting high quality PHP apps - PHP UK 2019
Best practices for crafting high quality PHP apps - PHP UK 2019Best practices for crafting high quality PHP apps - PHP UK 2019
Best practices for crafting high quality PHP apps - PHP UK 2019
 
Crafting Quality PHP Applications (ConFoo YVR 2017)
Crafting Quality PHP Applications (ConFoo YVR 2017)Crafting Quality PHP Applications (ConFoo YVR 2017)
Crafting Quality PHP Applications (ConFoo YVR 2017)
 
iOS best practices
iOS best practicesiOS best practices
iOS best practices
 
Html JavaScript and CSS
Html JavaScript and CSSHtml JavaScript and CSS
Html JavaScript and CSS
 
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
 
Javascript
JavascriptJavascript
Javascript
 
Domain Driven Design and Hexagonal Architecture with Rails
Domain Driven Design and Hexagonal Architecture with RailsDomain Driven Design and Hexagonal Architecture with Rails
Domain Driven Design and Hexagonal Architecture with Rails
 
Java script
Java scriptJava script
Java script
 
Advisor Jumpstart: JavaScript
Advisor Jumpstart: JavaScriptAdvisor Jumpstart: JavaScript
Advisor Jumpstart: JavaScript
 
Programming Primer EncapsulationVB
Programming Primer EncapsulationVBProgramming Primer EncapsulationVB
Programming Primer EncapsulationVB
 
Java script
Java scriptJava script
Java script
 
Philipp Von Weitershausen Untested Code Is Broken Code
Philipp Von Weitershausen   Untested Code Is Broken CodePhilipp Von Weitershausen   Untested Code Is Broken Code
Philipp Von Weitershausen Untested Code Is Broken Code
 
Refactoring
RefactoringRefactoring
Refactoring
 
Java script basics
Java script basicsJava script basics
Java script basics
 
Complete Notes on Angular 2 and TypeScript
Complete Notes on Angular 2 and TypeScriptComplete Notes on Angular 2 and TypeScript
Complete Notes on Angular 2 and TypeScript
 
Java script
Java scriptJava script
Java script
 

Similar a Unbreakable Domain Models PHPUK 2014 London

Similar a Unbreakable Domain Models PHPUK 2014 London (20)

Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13
 
Beyond MVC: from Model to Domain
Beyond MVC: from Model to DomainBeyond MVC: from Model to Domain
Beyond MVC: from Model to Domain
 
Crafting Quality PHP Applications (Bucharest Tech Week 2017)
Crafting Quality PHP Applications (Bucharest Tech Week 2017)Crafting Quality PHP Applications (Bucharest Tech Week 2017)
Crafting Quality PHP Applications (Bucharest Tech Week 2017)
 
Crafting Quality PHP Applications (PHP Benelux 2018)
Crafting Quality PHP Applications (PHP Benelux 2018)Crafting Quality PHP Applications (PHP Benelux 2018)
Crafting Quality PHP Applications (PHP Benelux 2018)
 
Crafting Quality PHP Applications (PHPkonf 2018)
Crafting Quality PHP Applications (PHPkonf 2018)Crafting Quality PHP Applications (PHPkonf 2018)
Crafting Quality PHP Applications (PHPkonf 2018)
 
Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Unit testing @ WordPress Meetup Tilburg 7 januari 2014Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Unit testing @ WordPress Meetup Tilburg 7 januari 2014
 
Domain-driven Design in PHP and Symfony - Drupal Camp Wroclaw!
Domain-driven Design in PHP and Symfony - Drupal Camp Wroclaw!Domain-driven Design in PHP and Symfony - Drupal Camp Wroclaw!
Domain-driven Design in PHP and Symfony - Drupal Camp Wroclaw!
 
Clean tests good tests
Clean tests   good testsClean tests   good tests
Clean tests good tests
 
Best practices for crafting high quality PHP apps (ScotlandPHP 2018)
Best practices for crafting high quality PHP apps (ScotlandPHP 2018)Best practices for crafting high quality PHP apps (ScotlandPHP 2018)
Best practices for crafting high quality PHP apps (ScotlandPHP 2018)
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
Broadleaf Presents Thymeleaf
Broadleaf Presents ThymeleafBroadleaf Presents Thymeleaf
Broadleaf Presents Thymeleaf
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everything
 
Data Validation models
Data Validation modelsData Validation models
Data Validation models
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
Steady with ruby
Steady with rubySteady with ruby
Steady with ruby
 
Best practices for crafting high quality PHP apps (Bulgaria 2019)
Best practices for crafting high quality PHP apps (Bulgaria 2019)Best practices for crafting high quality PHP apps (Bulgaria 2019)
Best practices for crafting high quality PHP apps (Bulgaria 2019)
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 

Más de Mathias Verraes

Why Domain-Driven Design Matters
Why Domain-Driven Design MattersWhy Domain-Driven Design Matters
Why Domain-Driven Design Matters
Mathias Verraes
 

Más de Mathias Verraes (11)

Towards Modelling Processes
Towards Modelling ProcessesTowards Modelling Processes
Towards Modelling Processes
 
Modelling Heuristics
Modelling HeuristicsModelling Heuristics
Modelling Heuristics
 
Small Controlled Experiments
Small Controlled ExperimentsSmall Controlled Experiments
Small Controlled Experiments
 
Managed Technical Debt
Managed Technical DebtManaged Technical Debt
Managed Technical Debt
 
DDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
DDD Basics: Bounded Contexts, Modelling - Kortrijk EditionDDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
DDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
 
Why Domain-Driven Design Matters
Why Domain-Driven Design MattersWhy Domain-Driven Design Matters
Why Domain-Driven Design Matters
 
Practical Event Sourcing
Practical Event SourcingPractical Event Sourcing
Practical Event Sourcing
 
Domain-Driven Design Basics
Domain-Driven Design BasicsDomain-Driven Design Basics
Domain-Driven Design Basics
 
Model Storming Workshop PHP Benelux 2014
Model Storming Workshop PHP Benelux 2014Model Storming Workshop PHP Benelux 2014
Model Storming Workshop PHP Benelux 2014
 
Fighting Bottlencks with CQRS - ResearchGate
Fighting Bottlencks with CQRS - ResearchGateFighting Bottlencks with CQRS - ResearchGate
Fighting Bottlencks with CQRS - ResearchGate
 
DDDBE Modellathon 2013
DDDBE Modellathon 2013DDDBE Modellathon 2013
DDDBE Modellathon 2013
 

Último

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Último (20)

Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 

Unbreakable Domain Models PHPUK 2014 London

  • 2. A Map of the World
  • 3. London 3h train rides Kortrijk, Belgium Paris Amsterdam
  • 4. All models are wrong, but some are useful.
  • 5. I’m Mathias Verraes I'm an independent consultant. I help teams build enterprise web applications.
  • 8. Data Model ~= Structural Model ~= State ! Domain Model ~= Behavioral Model !
  • 10. The domain expert says “A customer must always have an email address.” * Could be different for your domain ** All examples are simplified
  • 11. Test fails class CustomerTest extends PHPUnit_Framework_TestCase! {! /** @test */! public function should_always_have_an_email()! {! ! $customer = new Customer();! ! assertThat(! $customer->getEmail(),! equalTo('jim@example.com') ! );! ! }! }
  • 12. Test passes 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') ! );! }! }
  • 13. Test fails 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’);! ! }! }
  • 14. final class Customer! {! private $email;! ! public function __construct($email)! {! $this->email = $email;! }! ! public function getEmail()! {! return $this->email;! }! }
  • 15. Test passes 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') ! );! }! }
  • 17. final class ProspectiveCustomer ! {! public function __construct()! {! // no email! }! }! ! final class PayingCustomer ! { ! public function __construct($email)! {! $this->email = $email;! }! }
  • 19. final class ProspectiveCustomer ! {! /** @return PayingCustomer */! public function convertToPayingCustomer($email)! { ! //...! }! }! ! final class PayingCustomer ! { ! //...! }
  • 20. The domain expert meant “A customer must always have a valid email address.”
  • 21. $customerValidator = new CustomerValidator;! if($customerValidator->isValid($customer)){! // ...! }
  • 22. Test fails class CustomerTest extends PHPUnit_Framework_TestCase! {! /** @test */! public function should_always_have_a_valid_email()! {! ! $this->setExpectedException(! 'InvalidArgumentException'! );! ! new Customer('malformed@email');! ! }! }
  • 23. Test passes final class Customer ! {! public function __construct($email)! {! if( /* boring validation stuff */) {! throw new InvalidArgumentException();! }! $this->email = $email;! }! }
  • 25. Test passes final class Email! {! private $email;! ! public function __construct($email)! {! if( /* boring validation stuff */) {! throw new InvalidArgumentException();! }! $this->email = $email;! }! ! public function __toString() ! {! return $this->email;! } ! }
  • 26. Test passes final class Customer! {! /** @var Email */! private $email;! ! public function __construct(Email $email)! {! $this->email = $email;! }! }
  • 27. Test passes class CustomerTest extends PHPUnit_Framework_TestCase! {! /** @test */! public function should_always_have_a_valid_email()! {! ! $this->setExpectedException(! ‘InvalidArgumentException’! );! ! new Customer(new Email(‘malformed@email’));! ! }! }
  • 30. The domain expert says “A customer orders products and pays for them.”
  • 31. $order = new Order;! $order->setCustomer($customer);! $order->setProducts($products);! $order->setStatus(Order::UNPAID);! ! ! // ...! ! ! $order->setPaidAmount(500);! $order->setPaidCurrency(‘EUR’);! ! $order->setStatus(Order::PAID);! !
  • 32. $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)! );
  • 33. $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)! );
  • 34. $order = new Order($customer, $products);! // set PaymentStatus in Order::__construct()! ! ! ! ! ! ! ! $order->setPaidMonetary(! new Money(500, new Currency(‘EUR’))! );! $order->setStatus(! new PaymentStatus(PaymentStatus::PAID)! );
  • 35. $order = new Order($customer, $products);! ! ! ! ! ! ! ! ! $order->pay(! new Money(500, new Currency(‘EUR’))! );! // set PaymentStatus in Order#pay()! !
  • 38. The domain expert says “Premium customers get special offers.”
  • 40. The domain expert says “Order 3 times to become a premium customer.”
  • 41. interface CustomerSpecification ! {! /** @return bool */! public function isSatisfiedBy(Customer $customer); ! }
  • 42. 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! }!
  • 43. $customerIsPremium = new CustomerIsPremium;! ! $aCustomerWith2Orders = ...! $aCustomerWith3Orders = ...! ! assertFalse(! $customerIsPremium->isSatisfiedBy($aCustomerWith2Orders)! );! ! assertTrue(! $customerIsPremium->isSatisfiedBy($aCustomerWith3Orders)! );! ! !
  • 44. The domain expert says “Different rules apply for different tenants.”
  • 45. interface CustomerIsPremium ! extends CustomerSpecification! ! final class CustomerWith3OrdersIsPremium ! implements CustomerIsPremium! ! final class CustomerWith500EuroTotalIsPremium! implements CustomerIsPremium! ! final class CustomerWhoBoughtLuxuryProductsIsPremium! implements CustomerIsPremium! ! ...!
  • 46. final class SpecialOfferSender! {! private $customerIsPremium;! ! ! public function __construct(! CustomerIsPremium $customerIsPremium) {...}! ! ! public function sendOffersTo(Customer $customer) ! {! if($this->customerIsPremium->isSatisfiedBy(! $customer! )) ! {! // send offers...! }! }! }!
  • 47. ! <!-- 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 -->! <service ! id=”special.offer.sender”! class=”SpecialOfferSender”>! <argument type=”service” id=”customer.is.premium”/>! </service>
  • 48. Use specifications to encapsulate rules about object selection
  • 49. The domain expert says “Get a list of all premium customers.”
  • 50. 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);! }!
  • 51. Use repositories to create the illusion of in-memory collections
  • 52. interface CustomerRepository! {! ! /** @return Customer[] */! public function findSatisfying(! CustomerSpecification $customerSpecification! );! ! }! ! ! // generalized:! $objects = $repository->findSatisfying($specification);!
  • 53. class DbCustomerRepository implements CustomerRepository! {! /** @return Customer[] */! public function findSatisfying(! CustomerSpecification $specification) ! {! ! return array_filter(! $this->findAll(),! function(Customer $customer) use($specification) {! return $specification->isSatisfiedBy($customer);! } ! );! ! }! }!
  • 54. final class CustomerWith3OrdersIsPremium! implements CustomerSpecification! {! public function asSql() {! return ‘SELECT * FROM Customer...’;! }! }! ! ! // class DbCustomerRepository ! public function findSatisfying($specification) ! {! return $this->db->query($specification->asSql()); } !
  • 55. Use double dispatch to preserve encapsulation
  • 56. $expectedCustomers = array_filter(! $repository->findAll(),! // filter…! );! ! $actualCustomers = ! $repository->findSatisfying($specification);! ! assertThat($expectedCustomers, equalTo($actualCustomers));
  • 57. Test by comparing different representations of the same rule
  • 58. Protect your invariants ! Objects as consistency boundaries ! Encapsulate state and behavior
  • 59. Thanks! Questions? I ♥ Feedback joind.in/10690 ! Blog, Slides, other talks: verraes.net @mathiasverraes