$30 off During Our Annual Pro Sale. View Details »

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

    View Slide

  2. We ♥ Acronyms
    OOP
    DRY
    KISS
    YAGNI
    2

    View Slide

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

    View Slide

  4. We ♥ Acronyms
    OOP
    DRY
    KISS
    YAGNI
    4

    View Slide

  5. GRASP
    5

    View Slide

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

    View Slide

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

    View Slide

  8. SOLID
    8

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. 12
    Why?

    View Slide

  13. 13

    View Slide

  14. 14

    View Slide

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

    View Slide

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

    View Slide

  17. 17

    View Slide

  18. 18

    View Slide

  19. 19
    Story Time

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  29. 29
    Can you log messages
    that you send?

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  33. 33

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  47. 47
    You’ve
    Got to Be
    Kidding
    Me!

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  53. Credits
    ▪ Presentation template by SlidesCarnival
    ▪ Photographs by Unsplash
    53

    View Slide