Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ship 10 Times Faster With These Designs

Ship 10 Times Faster With These Designs

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.

B3b2139e4f2c0eca4efe2379fcebc1c5?s=128

Anna Filina
PRO

February 25, 2021
Tweet

Transcript

  1. Ship 10 Times Faster With These Designs CONFOO 2021 |

    FEB 25, 2021 @afilina
  2. Anna Filina / @afilina • Coding since 1997. • PHP

    since 2003. • Legacy archaeology. • Test automation. • Talks and workshops. • YouTube videos. • Filina Consulting.
  3. 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.
  4. Waiting for all the information before starting work.

  5. Knowns • Payment Provider • Charge money • Refund money

    of a previous transaction
  6. Payment Provider charge

  7. Payment Provider charge (Money, Instrument) refund

  8. Payment Provider charge (Money, Instrument) refund (Money, PaymentId)

  9. Payment Provider charge (Money, Instrument) : PaymentId refund (Money, PaymentId)

  10. Payment Provider charge (Money, Instrument) : PaymentId refund (Money, PaymentId)

    : RefundId
  11. interface PaymentProvider { public function charge(Money $money, Instrument $instrument): PaymentId;

    }
  12. Are we able to use this interface?

  13. Submit Form field Form field POST /payments/capture

  14. Validate input Controller Charge via PaymentProvider Request Response Send output

  15. final class CapturePaymentHandler { public function handle(ServerRequestInterface $request): ResponseInterface {

    } }
  16. final class CapturePaymentHandler { public function handle(ServerRequestInterface $request): ResponseInterface {

    $paymentId = $this->paymentProvider->charge($money, $instrument); } }
  17. final class CapturePaymentHandler { public function handle(ServerRequestInterface $request): ResponseInterface {

    $money = Money::fromRequest($request); $instrument = Instrument::fromRequest($request); $paymentId = $this->paymentProvider->charge($money, $instrument); } }
  18. 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); } }
  19. { "instrument": { //... }, "money": { "currency": "USD", "amount":

    1050 } } { "paymentId": "CC-0001" }
  20. FE/BE Contract • Is input viable? • Is output viable?

    • Talk use cases.
  21. 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.
  22. Can't have multiple developers on one feature.

  23. 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); } }
  24. 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); } }
  25. 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); } }
  26. interface PaymentProvider { /** * @throws ChargeFailed */ public function

    charge(Money $money, Instrument $instrument): PaymentId; }
  27. $money = Money::fromRequest($request); $instrument = Instrument::fromRequest($request); try { $paymentId =

    $this->paymentProvider->charge($money, $instrument); } catch (ChargeFailed $exception) { return $this->createErrorResponse($exception->getMessage()); } return $this->createSuccessResponse($paymentId);
  28. Handlers + tests PaymentProvider implementation + tests Acceptance tests +

    wiring components Value objects + tests POST /payments/capture
  29. Can't have multiple developers on one feature. Create separate classes

    with clear contracts.
  30. We don't understand the domain.

  31. charge($money, $instrument) Money int $amount string $currency charge(int $amount, string

    $currency, string $cardNumber, string $cardExpiry, string $postalCode, …) Instrument Card $card Address $address
  32. PaymentProvider HttpClient send (Request) charge (Money, Instrument)

  33. Money Instrument PaymentProvider Domain

  34. Money Instrument PaymentProvider CapturePaymentHandler Application

  35. Money Instrument PaymentProvider CapturePaymentHandler HttpClient Infrastructure

  36. Create Small Classes & Methods • 10 statements per method.

    • Classes that can fit in your head.
  37. We don't understand the domain. Separate the domain from the

    other layers. Create small classes and methods.
  38. Bugs.

  39. Money Instrument PaymentProvider CapturePaymentHandler HttpClient Unit tests Unit tests Integration

    tests Acceptance tests
  40. final class CapturePaymentHandler { private StripePaymentProvider $paymentProvider; public function __construct()

    { $this->paymentProvider = new StripePaymentProvider(); } }
  41. 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(); } }
  42. final class CapturePaymentHandlerTest extends TestCase { protected function setUp(): void

    { $this->paymentProvider = $this->createMock(PaymentProvider::class); $this->capturePaymentHandler = new CapturePaymentHandler( $this->paymentProvider ); } }
  43. Bugs. Just write tests. Learn to write even better tests.

  44. @afilina