Slide 1

Slide 1 text

Applied Design Patterns: Rolling with the Punches 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

2 Story Time

Slide 3

Slide 3 text

3 We want to send JSON data through Kinesis streams.

Slide 4

Slide 4 text

4 Pause

Slide 5

Slide 5 text

5 Acronyphilia?

Slide 6

Slide 6 text

We ♥ Acronyms OOP DRY YAGNI KISS 6

Slide 7

Slide 7 text

Keep it Simple? ▪ To Understand? ▪ To Write? ▪ To Instantiate? 7 ▪ To Maintain? ▪ To Test? ▪ To Change?

Slide 8

Slide 8 text

Keep it Simple? ▪ To Understand? ▪ To Write? ▪ To Instantiate? 8 ▪ To Maintain? ▪ To Test? ▪ To Change?

Slide 9

Slide 9 text

SOLID 9

Slide 10

Slide 10 text

SOLID ▪ S ▪ O ▪ L ▪ I ▪ D 10

Slide 11

Slide 11 text

SOLID ▪ SRP ▪ OCP ▪ LSP ▪ ISP ▪ DIP 11

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪ Interface Segregation ▪ Dependency Inversion 13 Does one thing

Slide 14

Slide 14 text

SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪ Interface Segregation ▪ Dependency Inversion 14 Extend without modification

Slide 15

Slide 15 text

SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪ Interface Segregation ▪ Dependency Inversion 15 Child can replace a parent

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪ Interface Segregation ▪ Dependency Inversion 17 Rely on abstractions

Slide 18

Slide 18 text

18 Why?

Slide 19

Slide 19 text

19

Slide 20

Slide 20 text

20

Slide 21

Slide 21 text

21

Slide 22

Slide 22 text

22

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Applied Design Patterns: Rolling with the Punches O S L I D O P R Y S I K N G A Y S I R P D I

Slide 26

Slide 26 text

Applied Design Patterns: Rolling with the Punches O S L I D O P R Y S I K N G A Y S I R P D I

Slide 27

Slide 27 text

27

Slide 28

Slide 28 text

28

Slide 29

Slide 29 text

29

Slide 30

Slide 30 text

30 Remember Our Story?

Slide 31

Slide 31 text

31 We want to send JSON data through Kinesis streams.

Slide 32

Slide 32 text

32 // The Simple Approach class KinesisStream { func new(KinesisClient client, string streamName); func write(Message message): void; } class Message { func new(map data); func getData(): map; func toJson(): string; } // Example stream = new KinesisStream(client, "app-events"); stream.write(new Message(["foo": "bar"])); YAGNI SRP

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

34 // Adding New Functionality? class Stream { func new(KinesisClient kinesisClient, SqsClient sqsClient, string streamName, string streamType ); func write(Message message): void { if (this.streamType === "kinesis") this.kinesisClient.(…) elif (this.streamType === "sqs") this.sqsClient.(…) else throw new Exception("…"); } } SRP OCP

Slide 35

Slide 35 text

35 // Adding New Functionality? class Stream { func new(KinesisClient kinesisClient, SqsClient sqsClient, string streamName, string streamType ); func write(Message message): void { if (this.streamType === "kinesis") this.kinesisClient.(…) elif (this.streamType === "sqs") this.sqsClient.(…) else throw new Exception("…"); } } SRP OCP

Slide 36

Slide 36 text

36 // Interfaces And Multiple Implementations interface Stream { func write(Message message): void; } class KinesisStream implements Stream { func new(KinesisClient client, string streamName); func write(Message message): void; } class SqsStream implements Stream { func new(SqsClient client, string queueUrl); func write(Message message): void; } SRP ISP

Slide 37

Slide 37 text

37 // Factories Are Your Friends class EventsFactory { func createEventStream(): Stream { switch (this.config.get("events.stream.type")) { case "kinesis": return this.createKinesisStream(); case "sqs": return this.createSqsStream(); // ... } func createKinesisStream(): KinesisStream; func createSqsStream(): SqsStream; } SRP LSP

Slide 38

Slide 38 text

38 // Polymorphism Can Be Easy class OrdersController { func new(Stream stream, OrdersRepository orders) {/* ... */} // ... func doSomethingWithAnOrder(string orderId) { // ... order = this.orders.get(orderId); this.stream.write(new Message([ "order_id": order.id, "item_count": order.items.count(), ])); // ... } } DIP

Slide 39

Slide 39 text

39 Can you log messages that you send?

Slide 40

Slide 40 text

40 // Decorators Are Cool class LoggingStream implements Stream { func new(Stream stream, LoggerInterface logger); func 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 LSP DIP

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

42 // Aggregates Can Work Too class MultiStream implements Stream { func new(Stream ...streams); func write(Message message): void; } // Example stream = new LoggingStream( new MultiStream(kinesisStream, sqsStream), logger ); stream.write(message); SRP OCP LSP DIP

Slide 43

Slide 43 text

43

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

45 // Messages Get a Structure class Message extends JsonSerializable { func new(Header header, array data); func getHeader(): Header; func getData(): map; func toJson(): string; } class Header extends JsonSerializable { func new(string id, …, DateTime timestamp); func getId(): string; func getSource(): string; func getTopic(): string; func getAction(): string; func getTimestamp(): DateTime; func toJson(): string; }

Slide 46

Slide 46 text

46 // A New Layer of Abstraction class EventPublisher { func new(Stream stream, string source); func publish(Event event): void; } interface Event { func getTopic(): string; func getAction(): string; func getData(): map; } publisher.publish(event); SRP DIP

Slide 47

Slide 47 text

47 // A New Layer of Abstraction class OrderEvent implements Event { func new(Order order, string action); // ... } class OrderCreatedEvent extends OrderEvent { func new(Order order); // ... } publisher.publish(new OrderCreatedEvent(order)); OCP LSP

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

49 // Polymorphism Instead of Branching class EventPublisher { func new(Stream stream, string source, bool on = true) { // … } func publish(Event event): void { if (this.on) { // Do stuff. } }; } OCP

Slide 50

Slide 50 text

50 // Polymorphism Instead of Branching interface Publisher { func publish(Event event): void; } class EventPublisher { func new(Stream stream, string source); func publish(Event event): void; } class NullPublisher { func publish(Event event): void { // Do nothing. }; } LSP DIP

Slide 51

Slide 51 text

51 // Conditionalize Your Factory Instead of Code class EventsFactory { // ... func createEventPublisher(): Publisher { if (!this.config.get("events.enabled")) { return new NullPublisher(); } return new EventPublisher( this.createEventStream(), this.config.get("events.source") ); } } SRP LSP

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

53 // Under the Hood Changes interface TopicMapper { func mapTopic(string topic): string; } class KinesisStream implements Stream { func new(KinesisClient client, TopicMapper tm); func write(Message message): void; } class SqsStream implements Stream { func new(SqsClient client, TopicMapper tm); func write(Message message): void; } SRP LSP ISP

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

55 // New Streams? No Biggie! class HttpStream implements Stream { func new(HttpClient client, string url); func write(Message message): void; } class CustomServiceStream implements Stream { func new(CustomServiceClient client); func write(Message message): void; }

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

57 You’ve Got to Be Kidding Me!

Slide 58

Slide 58 text

58 // Is There a Good Way? class CustomServiceBatchStream extends CustomServiceStream { func write(Message message): void func flush(): void; } OCP

Slide 59

Slide 59 text

59 // Is There a Good Way? interface Stream { func write(Message message): void; func flush(): void; } class CustomServiceBatchStream implements Stream { func write(Message message): void func flush(): void; } ISP

Slide 60

Slide 60 text

60 // Multiple Interfaces & Additive Changes interface Stream { func write(Message message): void; } interface BatchStream extends Stream { func flush(): void; } class CustomServiceBatchStream implements BatchStream { func new(CustomServiceClient client); func write(Message message): void; func flush(): void; } ISP

Slide 61

Slide 61 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. 61 O S L I D I P P P P P R C S S

Slide 62

Slide 62 text

62 class MultiStream implements Stream { func new(Stream ...streams); func write(Message message): void { }; } // Example stream = new LoggingStream( new MultiStream(kinesisStream, sqsStream), logger ); stream.write(message); Can anyone guess the programming language?

Slide 63

Slide 63 text

63 class MultiStream implements Stream { public function __construct(Stream ...$streams) { }; public function write(Message $message): void { }; } // Example $stream = new LoggingStream( new MultiStream($kinesisStream, $sqsStream), $logger ); $stream->write($message); It’s PHP!

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Credits ▪ Presentation template by SlidesCarnival ▪ Photographs by Unsplash 65