Relentless Refactoring Strategic improvement through Domain-Driven Design Mark Rickerby
The year 2000 in web softwareContinuum of architectural confusion
brutalist structure
organic miasma
procedural PHP, Perl, ASP, etc...
spaghetti code jungle
J2EE frameworkstandards
indirection and bloat
The year 2000 in web softwareContinuum of architectural confusion
brutalist structure
organic miasma
“Elegance is not a dispensable luxury but a
factor that decides between success and
failure.”
Edsger Dijkstra
Convention over configurationGeneric Model-View-Controller separation
app/controllers/CategoriesController.phpapp/controllers/ProductsController.phpapp/models/Category.phpapp/models/Product.phpapp/templates/categories/*app/templates/products/*
What is the model?Are these data objects or domain objects?
app/controllers/CategoriesController.phpapp/controllers/ProductsController.phpapp/models/Category.phpapp/models/Product.phpapp/templates/categories/*app/templates/products/*
Anemic modelsThe result of leaky abstractions
— the app/models convention is a sensible default for basic CMS driven websites
— Active Record fools developers into thinking that persistence is the core concept of the ‘M’ in MVC
Eventual inconsistencyIf a shortcut exists it will be used
— when the software model does not capture rich relationships and behavior, controllers and templates expedite the process of adding new functionality
Eventual inconsistencyFramework defaults become inexact
app/controllers/CategoriesController.phpapp/controllers/CheckoutController.phpapp/controllers/ProductsController.phpapp/models/Category.phpapp/models/Product.phpapp/templates/categories/*app/templates/checkout/*app/templates/products/*
Ad-hoc developmentWhen features outgrow the framework
— when in doubt, developers add new behavior to existing methods
— bug-fixes and knowledge about system behavior accumulates in procedural code
Ad-hoc duplicationReliance on utility functions for common tasks
$units = $config->defaultWeightUnits;$w = explode(' ', $product->weight);
if ($w[1] != 'lbs') { $weight = convertWeight( $w[0], $units, 'lbs' );} else { $weight = $w[0];}
Ad-hoc behaviourIndependent services tangled inside existing interface
class Product extends ActiveRecord {
public function create($imported=false) { if ($imported) { // edge case when the // product is created from // a bulk import } } }
Discover missing conceptsRefactoring towards deeper insight
— focus on conceptual integrity
— be prepared to look at things in a different way
— constantly refine the language used to describe the software domain
A unified languageBuilding blocks for a fine grained model
Entity~ persistent ‘noun’ with identity and stateeg: product, category
Value~ atomic object with no unique identityeg: price, currency, weight
Service~ stateless ‘verb’, action or process
Aggregate~ a cluster of related objects
Ad-hoc duplicationReliance on utility functions for common tasks
$units = $config->defaultWeightUnits;$w = explode(' ', $product->weight);
if ($w[1] != 'lbs') { $weight = convertWeight( $w[0], $units, 'lbs' );} else { $weight = $w[0];}
Small values Immutable objects that model basic domain concepts
app/model/Product.phpapp/model/Weight.php
$weight = $product->weight->lbs();
Ad-hoc behaviourIndependent services tangled inside existing interface
class Product extends ActiveRecord {
public function create($imported=false) { if ($imported) { // edge case when the // product is created from // a bulk import } } }
Service layerBreak out standalone tasks into transactional interface
class ProductImporter extends BulkImporter { public function importProduct($product) { // no longer an edge case }}
Aggregate rootsDivide complex entities into a more granular model
app/model/Attribute.phpapp/model/AttributeValue.phpapp/model/Product.phpapp/model/ProductAttribute.phpapp/model/ProductDetails.phpapp/model/Sku.php
class Product extends Aggregate {}
Benefits of a richer modelWhy use object oriented design anyway?
— easier to localize changes when behavior and data are close together
— negotiate complexity with precise language
— objects have a single responsibility; easier to understand and maintain
Design is continuousChange code based on improved understanding
— models are dynamic;always changing and growing
— models are explanatory;define domain knowledge in code
AmbiguitiesContradictory concepts in the domain language
— avoid trying to build a monolithic model that captures every aspect of the domain
— tackle ambiguity by grouping the model into Bounded Contexts
Model namespaceGrouping based on pattern types
app/model/collections/ProductList.phpapp/model/entities/Product.phpapp/model/services/ShippingProvider.phpapp/model/services/TaxRate.phpapp/model/values/Weight.phpapp/model/values/Price.php
Model namespaceGrouping based on context boundaries
app/model/Invoicing/TaxRate.phpapp/model/Shipping/Provider.phpapp/model/Storefront/Price.phpapp/model/Storefront/Product.phpapp/model/Storefront/ProductList.phpapp/model/Storefront/Weight.php
Shared coreExtract common behaviour into a shared context
app/model/Core/Price.phpapp/model/Core/Weight.phpapp/model/Core/Product.phpapp/model/Invoicing/TaxRate.phpapp/model/Shipping/Provider.phpapp/model/Storefront/ProductList.php
HomonymsOverloaded concepts in the domain language
— the same word is used to describe many different concepts
— the same concept has many different responsibilities
Bounded contextsAllow ambiguous concepts to co-exist
app/model/Core/Price.phpapp/model/Core/Weight.phpapp/model/Core/Product.phpapp/model/Invoicing/TaxRate.phpapp/model/Shipping/Product.phpapp/model/Shipping/Provider.phpapp/model/Storefront/Product.phpapp/model/Storefront/ProductList.php
Application boundariesRefactoring patterns for evolving architecture
Introduce Strangler~new abstraction that overtakes the host system like a strangling vine
Extract Service~spawn a separate application by extracting a service from the original system
SummaryRelentless improvement
Separate infrastructure from the model
Use precise language to define domain concepts
Model complex behaviour in bounded contexts