Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

We ♥ Acronyms OOP DRY KISS YAGNI 2

Slide 3

Slide 3 text

Simple... ▪ To Understand? ▪ To Write? ▪ To Instantiate? 3 ▪ To Maintain? ▪ To Test? ▪ To Change?

Slide 4

Slide 4 text

We ♥ Acronyms OOP DRY KISS YAGNI 4

Slide 5

Slide 5 text

GRASP 5

Slide 6

Slide 6 text

GRASP ▪ General ▪ Responsibility ▪ Assignment ▪ Software ▪ Patterns 6

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

SOLID 8

Slide 9

Slide 9 text

SOLID ▪ S ▪ O ▪ L ▪ I ▪ D 9

Slide 10

Slide 10 text

SOLID ▪ SRP ▪ OCP ▪ LSP ▪ ISP ▪ DIP 10

Slide 11

Slide 11 text

SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪ Interface Segregation ▪ Dependency Inversion 11 }Principle

Slide 12

Slide 12 text

12 Why?

Slide 13

Slide 13 text

13

Slide 14

Slide 14 text

14

Slide 15

Slide 15 text

Jeremy’s Theorem Good OO Design is like a nice set of Quick-Assembly Shelves 15

Slide 16

Slide 16 text

SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪ Interface Segregation ▪ Dependency Inversion 16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

18

Slide 19

Slide 19 text

19 Story Time

Slide 20

Slide 20 text

20 We want to send JSON data through Kinesis streams.

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

22 We might be switching to SQS, actually. Can you support both?

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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("…"); } }

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

28 // Optional: Anonymous Classes $echoStream = new class() implements Stream { function write(Message $message): void { echo json_encode($message, JSON_PRETTY_PRINT); } } ISP

Slide 29

Slide 29 text

29 Can you log messages that you send?

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

31 We are moving to SQS, but some systems haven’t yet. Can you send to both for now?

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

33

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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; }

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

38 We’d like to be able to turn events off via configuration.

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

42 We want different streams for some topics, so they can scale independently.

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

44 We’re moving to a custom streaming service now. Can you support that?

Slide 45

Slide 45 text

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; }

Slide 46

Slide 46 text

46 We want to send lots of messages at a time. Can we send them in batches?

Slide 47

Slide 47 text

47 You’ve Got to Be Kidding Me!

Slide 48

Slide 48 text

48 // Is There a Good Way? class CustomServiceBatchStream extends CustomServiceStream { public function write(Message $message): void public function flush(): void; } OCP

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

52 Thanks! You can find me at ▪ @jeremeamia ▪ [email protected]

Slide 53

Slide 53 text

Credits ▪ Presentation template by SlidesCarnival ▪ Photographs by Unsplash 53