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.

Ca57a7cfac69ba3abf517470f3770aae?s=128

Jeremy Lindblom

February 20, 2019
Tweet

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. We ♥ Acronyms OOP DRY KISS YAGNI 2

  3. Simple... ▪ To Understand? ▪ To Write? ▪ To Instantiate?

    3 ▪ To Maintain? ▪ To Test? ▪ To Change?
  4. We ♥ Acronyms OOP DRY KISS YAGNI 4

  5. GRASP 5

  6. GRASP ▪ General ▪ Responsibility ▪ Assignment ▪ Software ▪

    Patterns 6
  7. GRASP ▪ High Cohesion ▪ Low Coupling ▪ and more...

    7
  8. SOLID 8

  9. SOLID ▪ S ▪ O ▪ L ▪ I ▪

    D 9
  10. SOLID ▪ SRP ▪ OCP ▪ LSP ▪ ISP ▪

    DIP 10
  11. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 11 }Principle
  12. 12 Why?

  13. 13

  14. 14

  15. Jeremy’s Theorem Good OO Design is like a nice set

    of Quick-Assembly Shelves 15
  16. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 16
  17. 17

  18. 18

  19. 19 Story Time

  20. 20 We want to send JSON data through Kinesis streams.

  21. 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
  22. 22 We might be switching to SQS, actually. Can you

    support both?
  23. 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
  24. 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("…"); } }
  25. 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
  26. 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
  27. 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
  28. 28 // Optional: Anonymous Classes $echoStream = new class() implements

    Stream { function write(Message $message): void { echo json_encode($message, JSON_PRETTY_PRINT); } } ISP
  29. 29 Can you log messages that you send?

  30. 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
  31. 31 We are moving to SQS, but some systems haven’t

    yet. Can you send to both for now?
  32. 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
  33. 33

  34. 34 We’ve come up with a specific format for messages

    that contains metadata.
  35. 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; }
  36. 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
  37. 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
  38. 38 We’d like to be able to turn events off

    via configuration.
  39. 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
  40. 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
  41. 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
  42. 42 We want different streams for some topics, so they

    can scale independently.
  43. 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
  44. 44 We’re moving to a custom streaming service now. Can

    you support that?
  45. 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; }
  46. 46 We want to send lots of messages at a

    time. Can we send them in batches?
  47. 47 You’ve Got to Be Kidding Me!

  48. 48 // Is There a Good Way? class CustomServiceBatchStream extends

    CustomServiceStream { public function write(Message $message): void public function flush(): void; } OCP
  49. 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
  50. 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
  51. 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
  52. 52 Thanks! You can find me at ▪ @jeremeamia ▪

    jeremeamia@gmail.com
  53. Credits ▪ Presentation template by SlidesCarnival ▪ Photographs by Unsplash

    53