How to (finally) start doing
DDD by using BDD
Kacper Gunia @cakper
So!ware Engineer @SensioLabsUK / @Inviqa
PHPers Silesia @PHPersPL
What is BDD?
Bug-drivenDevelopment ;)
Behaviour-driven development is about implementing an application
by describing its behaviour from the perspective of its
stakeholders.-- Dan North
BDD is about establishing a shared understanding of “done”
working from the outside in until you get there
-- Dan North
BDD shows you what to do nextaka Technical Discipline
How do we BDD?
Feature: Traveler searches for cheap itineraries In order to save money while travelling As a world traveler I want to search for the cheapest itinerary
Product owner writes scenarioand developer automates it
Developer writes scenarioand then automates it
No!
BDD is about communication!
flickr.com/photos/dvids/5638829762
Scenario: Successfully find cheapest direct flight Given the flight from "WAW" to "LHR" priced $30 was scheduled And the flight from "WAW" to "LHR" priced $50 was scheduled When I open the "/search" page And I fill "WAW" in the "Departure airport" field And I fill "LHR" in the "Destination airport" field And I click "Search" Then I should be redirected to "/results" page And I should see $30 in the "#cheapest-flight-price" block
Scenario: Successfully find cheapest direct flight Given the flight from "WAW" to "LHR" priced $30 was scheduled And the flight from "WAW" to "LHR" priced $50 was scheduled When I open the "/search" page And I fill "WAW" in the "Departure airport" field And I fill "LHR" in the "Destination airport" field And I click "Search" Then I should be redirected to "/results" page And I should see $30 in the "#cheapest-flight-price" block
Translation
Can we do better?
Mission accomplished BoysWe can go home now!
flickr.com/photos/dvids/5638829762
Translation again
How to fix that?
DDD
What is DDD about?
It’s about focusing on the domain and letting it affect the so"ware very
much-- Jimmy Nilsson
But WHY do we need it?
Everybody knows the jargonin their OWN FIELD
It's about common understanding
Ubiquitous language
Concrete examples are rooted in the problem domain
-- Matt Wynne
Domain Model
A domain model (...) is not just the knowledge in a domain expert’s head;
it is a rigorously organized and selective abstraction of that knowledge
-- Eric Evans
Model documentsthe knowledge
Pushing for ubiquitous language hard enough makes your examples a domain
model-- Konstantin Kudryashov
Scenario: Successfully find cheapest direct flight Given the flight from "WAW" to "LHR" priced $30 was scheduled And the flight from "WAW" to "LHR" priced $50 was scheduled When I open the "/search" page And I fill "WAW" in the "Departure airport" field And I fill "LHR" in the "Destination airport" field And I click "Search" Then I should be redirected to "/results" page And I should see $30 in the "#cheapest-flight-price" block
Scenario: Successfully find cheapest direct itinerary Given the search for the itinerary schedule And the itinerary from "WAW" to "LHR" priced $30 was planned in the schedule And the itinerary from "WAW" to "LHR" priced $50 was planned in the schedule When I search for cheapest itinerary from "WAW" to "LHR" Then the cheapest itinerary should cost $30
Modelling by example
Phase 1
Scenario: Successfully find cheapest direct itinerary Given the search for the itinerary schedule And the itinerary from "WAW" to "LHR" priced $30 was planned in the schedule And the itinerary from "WAW" to "LHR" priced $50 was planned in the schedule When I search for cheapest itinerary from "WAW" to "LHR" Then the cheapest itinerary should cost $30
Given the search for the itinerary schedule
/** * @Given /^the search for the itinerary schedule$/ */ public function theSearchForTheItinerarySchedule() { $this->itinerarySchedule = new ItinerarySchedule(); $this->search = new Search($this->itinerarySchedule); }
Design emerges
And the itinerary from "WAW" to "LHR" priced $30 was planned in the schedule
/** * @Given the itinerary from :fromAirport to :toAirport * priced $:price was planned in the schedule */ public function theItineraryFromToPricedWasPlannedInTheSchedule( $fromAirport, $toAirport, $price ) { $itinerary = new Itinerary( Airport::code($fromAirport), Airport::code($toAirport), Money::usd($price) );
$this->itinerarySchedule->plan($itinerary); }
When I search for cheapest itinerary from "WAW" to "LHR"
/** * @When I search for cheapest itinerary from :fromAirport to :toAirport */ public function iSearchForCheapestItineraryFromTo($fromAirport, $toAirport) { $this->cheapestItinerary = $this->search->forCheapest( Airport::code($fromAirport), Airport::code($toAirport) ); }
Then the cheapest itinerary should cost $30
/** * @Then the cheapest itinerary should cost $:price */ public function theCheapestItineraryShouldCost($price) { expect($this->cheapestItinerary->cost())->toBeLike(Money::usd($price)); }
Phase 2
@ui Scenario: Successfully find cheapest direct itinerary Given the search for the itinerary schedule And the itinerary from "WAW" to "LHR" priced $30 was planned in the schedule And the itinerary from "WAW" to "LHR" priced $50 was planned in the schedule When I search for cheapest itinerary from "WAW" to "LHR" Then the cheapest itinerary should cost $30
Given the search for the itinerary schedule
/** * @Given the search for the itinerary schedule */ public function theSearchForTheItinerarySchedule() { $this->visit("/search"); }
And the itinerary from "WAW" to "LHR" priced $30 was planned in the schedule
/** * @Given the itinerary from :fromAirport to :toAirport * priced $:price was planned in the schedule */ public function theItineraryFromToPricedWasPlannedInTheSchedule( $fromAirport, $toAirport, $price ) { $itinerary = new Itinerary( Airport::code($fromAirport), Airport::code($toAirport), Money::usd($price) );
$this->get("itinerary_schedule")->plan($itinerary); }
When I search for cheapest itinerary from "WAW" to "LHR"
/** * @When I search for cheapest itinerary from :fromAirport to :toAirport */ public function iSearchForCheapestItineraryFromTo($fromAirport, $toAirport) { $this->fillIn("#from-airport", $fromAirport); $this->fillIn("#to-airport", $toAirport);
$this->clickButton("Search"); }
Then the cheapest itinerary should cost $30
/** * @Then the cheapest itinerary should cost $:price */ public function theCheapestItineraryShouldCost($price) { $cheapestItinerary = $this->find("#cheapest-itinerary"); expect($cheapestItinerary)->toContainText(sprintf("From $%s", $price)); }
# behat.yml default: suites: domain: contexts: [ SearchContext ] ui: contexts: [ WebSearchContext ] filters: { tags: '@ui' }
Modelling by ExampleIn three (easy) steps
· Have the conversation· Model your objects
· Go again through UI*
*But
You (really) don't have to automate
everything!
But there is a problem
We ignored the depth of the domain
On purpose
You can't model the whole system using
one feature
Repeat the process and model the
planner
What if model has different requirements
in this context?
Bounded Context
Language is limited
Search Itinerary!=
Planner Itinerary!=
Booking Itinerary
Do not build fragile monoliths!
Build applications withBounded context in mind
--- Wrap up ---
Have the conversation
Do not separate the concepts from the implementation
You cannot build conceptual models
without considering implementation issues
Push forUbiquitous language
Use Behat to drive your ModelNot only the UI
"BDD is about conversations you have to
produce software"
"DDD is about how youexplore domain models
and how you articulate this"