Ship 10 Times Faster
With These Designs
CONFOO 2021 | FEB 25, 2021
@afilina
Slide 2
Slide 2 text
Anna Filina / @afilina
• Coding since 1997.
• PHP since 2003.
• Legacy archaeology.
• Test automation.
• Talks and workshops.
• YouTube videos.
• Filina Consulting.
Slide 3
Slide 3 text
Your competition will hate you. By the time they release one
feature with a ton of bugs, you would have released 10
features with no bugs. A good design makes a world of
difference in terms of ease of change, teamwork, testability,
reliability and overall quality of life. I will present a software
design approach that allowed my teams to ship unbelievably
fast while keeping the quality above industry standards.
Slide 4
Slide 4 text
Waiting for all the information before starting work.
Slide 5
Slide 5 text
Knowns
• Payment Provider
• Charge money
• Refund money of a previous transaction
interface PaymentProvider
{
public function charge(Money $money, Instrument $instrument): PaymentId;
}
Slide 12
Slide 12 text
Are we able to use this interface?
Slide 13
Slide 13 text
Submit
Form field
Form field
POST
/payments/capture
Slide 14
Slide 14 text
Validate input
Controller
Charge via PaymentProvider
Request Response
Send output
Slide 15
Slide 15 text
final class CapturePaymentHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
}
}
Slide 16
Slide 16 text
final class CapturePaymentHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$paymentId = $this->paymentProvider->charge($money, $instrument);
}
}
Slide 17
Slide 17 text
final class CapturePaymentHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$money = Money::fromRequest($request);
$instrument = Instrument::fromRequest($request);
$paymentId = $this->paymentProvider->charge($money, $instrument);
}
}
Slide 18
Slide 18 text
final class CapturePaymentHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$money = Money::fromRequest($request);
$instrument = Instrument::fromRequest($request);
$paymentId = $this->paymentProvider->charge($money, $instrument);
return $this->createSuccessResponse($paymentId);
}
}
FE/BE Contract
• Is input viable?
• Is output viable?
• Talk use cases.
Slide 21
Slide 21 text
Waiting for all the information before starting work.
Postpone decisions using interfaces.
Write code for the things that you do know.
Validate assumptions using code.
Uncover new information or use cases.
Slide 22
Slide 22 text
Can't have multiple developers on one feature.
Slide 23
Slide 23 text
class CapturePaymentHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$money = Money::fromRequest($request);
$instrument = Instrument::fromRequest($request);
$paymentId = $this->paymentProvider->charge($money, $instrument);
return $this->createSuccessResponse($paymentId);
}
}
Slide 24
Slide 24 text
class CapturePaymentHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$money = Money::fromRequest($request);
$instrument = Instrument::fromRequest($request);
$paymentId = $this->paymentProvider->charge($money, $instrument);
return $this->createSuccessResponse($paymentId);
}
}
Slide 25
Slide 25 text
class CapturePaymentHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$money = Money::fromRequest($request);
$instrument = Instrument::fromRequest($request);
$paymentId = $this->paymentProvider->charge($money, $instrument);
return $this->createSuccessResponse($paymentId);
}
}
Slide 26
Slide 26 text
interface PaymentProvider
{
/**
* @throws ChargeFailed
*/
public function charge(Money $money, Instrument $instrument): PaymentId;
}
Create Small Classes & Methods
• 10 statements per method.
• Classes that can fit in your head.
Slide 37
Slide 37 text
We don't understand the domain.
Separate the domain from the other layers.
Create small classes and methods.
Slide 38
Slide 38 text
Bugs.
Slide 39
Slide 39 text
Money
Instrument
PaymentProvider
CapturePaymentHandler
HttpClient
Unit tests
Unit tests
Integration tests
Acceptance
tests
Slide 40
Slide 40 text
final class CapturePaymentHandler
{
private StripePaymentProvider $paymentProvider;
public function __construct()
{
$this->paymentProvider = new StripePaymentProvider();
}
}
Slide 41
Slide 41 text
final class CapturePaymentHandler
{
private PaymentProvider $paymentProvider;
public function __construct(PaymentProvider $paymentProvider)
{
$this->paymentProvider = $paymentProvider;
}
}
final class CapturePaymentHandler
{
private StripePaymentProvider $paymentProvider;
public function __construct()
{
$this->paymentProvider = new StripePaymentProvider();
}
}
Slide 42
Slide 42 text
final class CapturePaymentHandlerTest extends TestCase
{
protected function setUp(): void
{
$this->paymentProvider = $this->createMock(PaymentProvider::class);
$this->capturePaymentHandler = new CapturePaymentHandler(
$this->paymentProvider
);
}
}
Slide 43
Slide 43 text
Bugs.
Just write tests.
Learn to write even better tests.