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

Protecting Your Code From A Mischievous Future

Protecting Your Code From A Mischievous Future

Your code is perfect! Well, at least until the next set of requirements come in and tries to "sweep the leg" of your beautiful codebase. However, by following the SOLID object-oriented design principles, you can minimize the churn or hacks needed as your code is forcefully evolved. Let's learn the concepts of SOLID in the context of some real code.

Jeremy Lindblom

February 20, 2019
Tweet

More Decks by Jeremy Lindblom

Other Decks in Programming

Transcript

  1. Protecting Your Code From a Mischievous Future O S L

    I D O P R Y S I K N G A Y S I R P D I
  2. Simple... ▪ To Understand? ▪ To Write? ▪ To Instantiate?

    3 ▪ To Maintain? ▪ To Test? ▪ To Change?
  3. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 11 }Principle
  4. 13

  5. 14

  6. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 16
  7. 17

  8. 18

  9. 21 // The Simple Approach class KinesisStream { function __construct(KinesisClient

    $client, string $streamName); function write(Message $message): void; } class Message implements JsonSerializable { function __construct(array $data); function getData(): array; function jsonSerialize(): array; } // Example $stream->write(new Message(['foo' => 'bar'])); YAGNI SRP
  10. 23 // Adding New Functionality? class Stream { function __construct(

    KinesisClient $kinesisClient, SqsClient $sqsClient, string $streamName, string $streamType ); function write(Message $message): void { if ($this->streamType === 'kinesis') $this->kinesisClient->… elseif ($this->streamType === 'sqs') $this->sqsClient->… else throw new Exception("…"); } } SRP OCP
  11. 24 // Adding New Functionality? class Stream { function __construct(

    KinesisClient $kinesisClient, SqsClient $sqsClient, string $streamName, string $streamType ); function write(Message $message): void { if ($this->streamType === 'kinesis') $this->kinesisClient->… elseif ($this->streamType === 'sqs') $this->sqsClient->… else throw new Exception("…"); } }
  12. 25 // Interfaces And Multiple Implementations interface Stream { function

    write(Message $message): void; } class KinesisStream implements Stream { function __construct(KinesisClient $client, string $streamName); function write(Message $message): void; } class SqsStream implements Stream { function __construct(SqsClient $client, string $queueUrl); function write(Message $message): void; } SRP
  13. 26 // Factories Are Your Friends class EventsFactory { function

    createEventStream(): Stream { switch ($this->config->get('events.stream')) { case 'kinesis': return $this->createKinesisStream(); case 'sqs': return $this->createSqsStream(); // ... } function createKinesisStream(): KinesisStream; function createSqsStream(): SqsStream; } SRP LSV
  14. 27 // Polymorphism Can Be Easy class OrdersController { public

    function __construct(Stream $stream) {/* ... */} // ... public function doSomethingWithAnOrder(string $orderId) { // ... $order = $this->orders->get($orderId); $stream->write(new Message([ 'order_id' => $order->id, 'item_count' => $order->items->count(), ])); // ... } } DIP
  15. 28 // Optional: Anonymous Classes $echoStream = new class() implements

    Stream { function write(Message $message): void { echo json_encode($message, JSON_PRETTY_PRINT); } } ISP
  16. 30 // Decorators Are Cool class LoggingStream implements Stream {

    function __construct(Stream $stream, LoggerInterface $logger); function write(Message $message): void { $this->stream->write($message); $this->logger->info('Message sent', $message->getData()); }; } // Example $stream = new LoggingStream($kinesisStream, $logger); $stream->write($message); SRP OCP LSV ISV DIP
  17. 31 We are moving to SQS, but some systems haven’t

    yet. Can you send to both for now?
  18. 32 // Aggregates Can Work Too class MultiStream implements Stream

    { function __construct(Stream ...$streams); function write(Message $message): void; } // Example $stream = new LoggingStream( new MultiStream($kinesisStream, $sqsStream, $echoStream), $logger ); $stream->write($message); SRP OCP LSV ISV DIP
  19. 33

  20. 35 // Messages Get a Structure class Message extends JsonSerializable

    { function __construct(Header $header, array $data); function getHeader(): Header; function getData(): array; function jsonSerialize(): array; } class Header extends JsonSerializable { function __construct(string $id, …, DateTime $timestamp); function getId(): string; function getSource(): string; function getTopic(): string; function getAction(): string; function getTimestamp(): DateTime; function jsonSerialize(): array; }
  21. 36 // A New Layer of Abstraction class EventPublisher {

    public function __construct(Stream $stream, string $source); public function publish(Event $event): void; } interface Event { public function getTopic(): string; public function getAction(): string; public function getData(): array; } $publisher->publish($event); SRP DIP
  22. 37 // A New Layer of Abstraction class OrderEvent implements

    Event { function __construct(Order $order, string $action); // ... } class OrderCreatedEvent extends OrderEvent { function __construct(Order $order); // ... } $publisher->publish(new OrderCreatedEvent($order)); OCP LSV
  23. 39 // Polymorphism Instead of Branching class EventPublisher { public

    function __construct( Stream $stream, string $source, bool $enabled = true ); public function publish(Event $event): void { if ($this->enabled) { // Do stuff. } }; } OCP
  24. 40 // Polymorphism Instead of Branching interface Publisher { public

    function publish(Event $event): void; } class EventPublisher { public function __construct(Stream $stream, string $source); public function publish(Event $event): void; } class NullPublisher { public function publish(Event $event): void { // Do nothing. }; } LSV DIP
  25. 41 // Conditionalize Your Factory Instead of Code class EventsFactory

    { // ... function createEventPublisher(): Publisher { if (!$this->config->get('events.enabled')) { return new NullPublisher(); } return new EventPublisher( $this->createEventStream(), $this->config->get('events.source') ); } } SRP LSV
  26. 43 // Under the Hood Changes interface TopicMapper { function

    mapTopic(string $topic): string; } class KinesisStream implements Stream { function __construct(KinesisClient $client, TopicMapper $tm); function write(Message $message): void; } class SqsStream implements Stream { function __construct(SqsClient $client, TopicMapper $tm); function write(Message $message): void; } SRP LSV
  27. 45 // New Streams? No Biggie! class HttpStream implements Stream

    { public function __construct(HttpClient $client); public function write(Message $message): void; } class CustomServiceStream implements Stream { public function __construct(CustomServiceClient $client); public function write(Message $message): void; }
  28. 46 We want to send lots of messages at a

    time. Can we send them in batches?
  29. 48 // Is There a Good Way? class CustomServiceBatchStream extends

    CustomServiceStream { public function write(Message $message): void public function flush(): void; } OCP
  30. 49 // Is There a Good Way? interface Stream {

    public function write(Message $message): void; public function flush(): void; } class CustomServiceBatchStream implements Stream { public function write(Message $message): void public function flush(): void; } ISP
  31. 50 // Multiple Interfaces & Additive Changes interface Stream {

    public function write(Message $message): void; } interface BatchStream extends Stream { public function flush(): void; } class CustomServiceBatchStream implements BatchStream { public function __construct(CustomServiceClient $client); public function write(Message $message): void; public function flush(): void; } ISP
  32. Final Thoughts ▪ Software is complicated, because life is complicated.

    ▪ You can’t predict the future, but you can prepare for it. ▪ Learn from others’ experiences, but experience it for yourself. 51 O S L I D I P P P P P R C S S