Conscious Coupling
Building Software that Lasts
Ciaran McNulty at SymfonyLive London
Me
Hi, I'm Ciaran and I'm a PHP developer
I am a consultant with Inviqa
I maintain PhpSpec
My early period:
Follow the framework!
SymfonyLive London 2013
Konstantin Kudryashov and Marcello Duarte -
The Framework as an Implementation Detail
SymfonyLive London 2014
Matthias Noback - The Naked Bundle
My middle period:
Decouple all the things!
Modelling by Example
Konstantin Kudryashov (again)
My current period:
Couple some of the things!
What is coupling?
class TaxiDispatcher {
function dispatch (Car $car, $destination)
{
$this->chargeCustomer();
$car->goTo($destination);
}
}
Problems with dependencies
—Changes to Car mean changes to Dispatcher
—Can't reuse Dispatcher with new vehicles
interface Vehicle
{
public function goTo($destination);
}
class TaxiDispatcher {
function dispatch (Vehicle $vehicle, $destination)
{
$this->chargeCustomer();
$vehicle->goTo($destination);
}
}
Benefits of abstraction
—Now we only need to rewrite Dispatcher and Car
when Vehicle changes
—Vehicle can be very stable; it's just an interface
—Can make new transportation types that
implement Vehicle
Defining abstractions
—Start with the use case, not the implementation
detail
—Contract should be a simple as possible
—Contract should focus on responsibilities
—Hide details of underlying APIs
Example - upgrade eligibility
Bad abstraction:
interface HttpClient
{
public function getData : array ($url, $parameters);
}
Example - upgrade eligibility
Better abstraction:
interface UpgradeEligibilityChecker
{
public function getContractId : int ($phoneNumber);
public function isEligibleForUpgrade : bool ($contractId);
}
Example - upgrade eligibility
Best abstraction:
interface UpgradeEligibilityChecker
{
public function isEligibleForUpgrade : bool ($phoneNumber);
}
Abstraction via interfaces
—You still need to know what methods to call
—Caller has to respond to changes in the callee
Taking it further:
Events and Commands
—Migrate actions from method calls to objects
—Depend on a bus rather than depending on
contracts
—Don't know anything about what handles the
action
Events
Direct coupling:
$notifier->notifyLawyersAboutPurchase($id, $item, $cost);
Event style (symfony/event-dispatcher):
$eventDispatcher->dispatch(
'item.purchased',
new ItemWasPurchased($id, $item, $cost);
);
Commands
Use case / service style:
$invoiceApprover->approveInvoice(1234);
Command style (thephpleague/tactician):
$commandBus->handle(
new ApproveInvoice(1234);
);
Advantages of decoupling via abstractions
—Cleaner APIs
—Swappable components
—Separation of concerns
—Easier to test
—No 'ripple effect' around changes
Disadvantages of decoupling via abstractions
—Makes execution flow harder to follow
—Adds more complexity (more files in codebase)
—Cognitive cost of thinking of good abstractions
Decoupling from
infrastructure
—Abstractions are going to change when the use
cases change
—Interfaces are more tightly coupled to code that
uses them
—Interfaces 'belong' in the same architectural
boundary as the code that uses them.
Frameworks
Coupling to other people's code
Highly coupled frameworks
Some frameworks let you go really quickly by
coupling directly to them.
You probably won't be able to reuse your code
without considerable effort.
—Drupal
—Magento
—Wordpress
More coupled framworks
Some frameworks let you go faster when you adopt
their conventions.
You can reuse your code with a fair amount of
additional effort.
—Symfony (1)
—CakePHP
—Laravel
More decoupled frameworks
Some frameworks encourage decoupling so your
code is more reusable.
You will go a little slower because you need to
explicitly configure things
—Symfony 2+
—Zend Framework 2+
Very decoupled frameworks
Some frameworks are extremely decoupled so code
is almost entirely reusable.
You almost have to construct the entire framework
yourself from components - this can be hard!
—D.I.Y.
—Silex
—Zend Expressive
Decoupling inside your
system
Only decouple where you
need to
(how do we know where that is?)
Coupled code get painful
when change happens
Decoupled code was
wasted effort when
change doesn't happen
Where does change come
from?
Where does change come from?
People!
Organizations which design
systems ... are constrained
to produce designs which
are copies of the
communication structures of
these organizations
— Melvin Conway, 1967
Decouple your system to
respond to different
streams of change
Service-orientation
—Good abstractions help you define boundaries
—Abstractions inside a monolith are easier to move
when you get it wrong
—Share values, not entities
—Don't share persistence
Context mapping
—Identify subdomains
—Identify which are:
—Core subdomains
—Supporting subdomains
—Generic subdomains
—Mirror the structure in your architecture
The bad news:
You will get it wrong
You only find out afterwards
The good news:
It's not just you
Things to check out
—Context Mapping
—Hexagonal Architecture
—Modelling by Example
Thank you!
—@ciaranmcnulty
—Lead Maintainer of PhpSpec
—Trainer and Coach at Inviqa
Questions?

Conscious Coupling