All systems change during their life cycles

 Ivar Jacobson 

Deliver not only fast

But predictably

SOLID

Uncle Bob

Bertrand Meyer

Barbara Liskov

David Parnas

The Open/Close Principle

A module should be open for extension but closed for modification.

The Open/Close Principle

Change behaviour by adding code, not changing it!

public function whenSomethingHappened(StatusWasChanged $event)
{
    $messageText = 'some message';
    $appointment = $this->appointments->findByAggregateId($event->aggregateId());
    foreach ($appointment->participants as $participant) {
        $this->sendEmail($messageText, $practice, $participant);
        $this->sendWebNotification('someEvent', $messageText, $participant);
    }
}

Story of one notification...

public function whenSomethingHappened(StatusWasChanged $event)
{
    $messageText = 'some message';
    $appointment = $this->appointments->findByAggregateId($event->aggregateId());
    foreach ($appointment->participants as $participant) {
        $this->sendEmail($messageText, $practice, $participant);
        if ($participant->phone()) {
            $this->sendSms($messageText, $participant->phone());
        }
        $this->sendWebNotification('someEvent', $messageText, $participant);
    }
}

Story of one notification...

public function whenSomethingHappened(StatusWasChanged $event)
{
    $messageText = 'some message';
    $appointment = $this->appointments->findByAggregateId($event->aggregateId());
    foreach ($appointment->participants as $participant) {
        if (!$participant->isAvailableForNotifications($practice->timezone())) {
            continue;
        }
        if ($participant->notifications()->someEvent()->email()) {
            $this->sendEmail($messageText, $practice, $participant);
        }
        if (
            $participant->notifications()->someEvent()->phone() &&
            $participant->phone()
        ) {
            $this->sendSms($messageText, $participant->phone());
        }
        if ($participant->notifications()->someEvent()->browser()) {
            $this->sendWebNotification('someEvent', $messageText, $participant);
        }
    }
}

Story of one notification...

public function whenSomethingHappened(StatusWasChanged $event)
{
    $appointment = $this->appointments->findByAggregateId($event->aggregateId());
    foreach ($appointment->participants as $participant) {
        $this->notifier->send(SomeNotification::for($participant));
    }
}

Story of one notification...

class Notifier
{
    // ...
    public function send(Notification $notification)
    {
        $this->notifications->remember(NotificationEntry::for($notification));
        
        if ($this->shouldNotDisturbFor($notification)) {
            return;
        }
        
        if ($this->isSmsNotificationsEnabled($notification)) {
            // send via sms
        }
        if ($this->isEmailNotificationsEnabled($notification)) {
            // send via email
        }
        if ($this->isBrowserNotificationsEnabled($notification)) {
            // send via websockets
        }
    }
}

Story of one notification...

Patterns

  • Strategy
  • State
  • Chain of Responsibility
  • Visitor
  • Decorator
  • Composition
  • Factory
  • Observer
  • ...

Points of extension

  • interfaces
    (to implement or to consume)

  • events

Module provides

Avoid

premature generalization

On higher level

Single Responsibility Principle

Each module should have one and only one reason to change

By "Change" we mean

Change in behaviour

Reason for change

As a doctor

I want daily digest
of all my upcoming activities

class Notifier
{
    // ...
    public function send(Notification $notification)
    {
        $this->notifications->remember(NotificationEntry::for($notification));
        
        if ($this->shouldNotDisturbFor($notification)) {
            return;
        }
        
        if ($this->isSmsNotificationsEnabled($notification)) {
            $this->smsNotifier->send($notification->message(), $notification->phone());
        }
        if ($this->isEmailNotificationsEnabled($notification)) {
            // delegate to email notifier
        }
        if ($this->isBrowserNotificationsEnabled($notification)) {
            // delegate to browser notifier
        }
    }
}

Delegation

is same thing

not everything that looks similar

Inheritance

Same type = Same Behaviour

Don't use inheritance
too much

The Liskov Substitution Principle

Subtypes should be substitutable for their base types

Contracts

respect them

interface DateTimeInterface 
{
    public function modify(): \DateTimeInterface;
    // ...
}

class DateTime implements DateTimeInterface
{
    public function modify(): \DateTimeInterface {
        return $this;
    }
}

class DateTimeImmutable implements DateTimeInterface
{
    public function modify(): \DateTimeInterface {
        return new self(...);
    }
}

LSP Violations

remove "modify" from interface

Avoid

premature generalization

Avoid

protected state
(only private)

Interface Segregation Principle

Many client specific interfaces are better than one general purpose interface

Not so good...

interface EntityManagerInterface extends ObjectManager
{
    public function getCache();
    public function getConnection();
    public function getExpressionBuilder();    public function beginTransaction();
    public function transactional($func);
    public function commit();
    public function rollback();
    public function createQuery($dql = '');
    public function createNamedQuery($name);
    public function createNativeQuery($sql, ResultSetMapping $rsm);
    public function createNamedNativeQuery($name);
    public function createQueryBuilder();
    public function getReference($entityName, $id);
    public function getPartialReference($entityName, $identifier);
    public function close();
    public function copy($entity, $deep = false);
    public function lock($entity, $lockMode, $lockVersion = null);
    public function getEventManager();
    public function getConfiguration();
    public function isOpen();
    public function getUnitOfWork();
    public function getHydrator($hydrationMode);
    public function newHydrator($hydrationMode);
    public function getProxyFactory();
    public function getFilters();
    public function isFiltersStateClean();
    public function hasFilters();
}

Dependency Inversion

Principle

Depend upon Abstractions. Do not depend upon concretions.

Stability is expressed

by the frequency of changes in behavior

Dependency Inversion

Principle

Dependency Inversion

Principle

interface PaymentMethod
{
    /**
     * Return true if implementation supports 
     * this payment method
     */
    public function supports(string $method): bool;

    /**
     * Generated invoice for given payment method
     */
    public function pay(Money $amount): Invoice;
}

Dependency Inversion

Principle

Dependency Inversion

Principle

The Stable Dependencies Principle

Depend in the direction of stability.

What about all that talk about screwing up future events?

Refactoring?

SOLID

It's not only about

Dependencies

as code smell

class RegisterCustomerHandler
{
    public function handle(RegisterUserCommand $command)
    {
         $user = $this->users->registerUser($command->userCredentials);

         $this->referrals->enroll($user->asId(), $command->referral);

         $this->orders->createCustomer($user->asId(), $command->customerInfo);
    }
}

Coordination

The Whole Value Pattern

  • Windspeed (NNW at 20 kph)
  • Temperature (75 degrees Fahrenheit)
  • Lightreading (1000 lumens)
  • etc...

GRASP

General responsibility assignment software patterns