Watertight Compartments

Structural design patterns to the rescue

Created by Ignasi 'Iggy' Bosch / @ignasibosch

  1. Introduction
  2. What
    • Assumptions
    • Watertight compartments
    • Bad examples
    • Good way
  3. How
    • Design Patterns
    • Adapter
    • Bridge
    • Proxy
    • Decorator
    • Facade

Ok, a couple of assumptions:

We assume that all applications want to remain active, and therefore to change and continue to grow.

We assume that every piece of code is sensitive to changes and then at the same time, is a candidate to change.

Decoupling

Clean code

S.O.L.I.D. principles

Hexagonal architecture

Onion architecture

Command and Query Responsibility Segregation (CQRS)

Watertight Compartments:

Bad examples:

AlertaController.php

public function initialize()
{
    /* Variable para obtener la uri del controlador desde los metodos/vistas */
    $this->myUri = 'alerta';
    parent::initialize();

    /* ACL Control */
   $this->returnIfNotAllowed("Alerta", "index");

   /* BreadCrumbs */
   $this->addBreadCrumb($this->labels['entities'][$this->myUri],
                            $this->config['app']['plataforma_uri'] . $this->myUri);  <--- infection
   $this->alertaService = $this->di->get('alertaService');
   $this->alertaRepository = $this->em->getRepository('Plataforma\Entities\Alerta'); <--- infection
   $this->formValidator = new AlertaFormValidator(); <--- infection
   $this->alertaLog = new AlertaLog(); <--- infection
   $this->alertaLog->setUser($this->session->get($this->config['auth']['session_key'])); <--- infection
}
                        
KitManager.php

public function __construct($em)
{
    $this->em = $em; <--- infection
    $this->kitRepository = $em->getRepository('Plataforma\Entities\Kit');  <--- infection
    $this->saveValidator = new KitSaveValidator(); <--- infection
}

/**
* @throws SaveValidationException
* @return \Plataforma\Entities\Kit
*/
public function create()
{
    /** @throws SaveValidationException */
    $this->saveValidator->validate($this->data);<--- infection
    $kit = new Kit();
    $kit->setNombre($this->data['nombre'])
        ->setDescripcion($this->data['descripcion'])
        ->setReferencia($this->data['referencia'])
        ->setSlug($this->data['slug'])
        ->setTipo($this->data['tipo'])
        ->setGuid(strtoupper(Uuid::generate()))<--- infection
        ->setEstado(isset($this->data['estado']))<--- infection
        ->setOrden($this->data['orden']);
    $this->em->persist($kit);<--- infection
    $this->em->flush();<--- infection
    return $kit;
}
                        
CupoController.php

public function createAction(Request $request)
{
    $entity = new Cupo();
    $form = $this->createCreateForm($entity);<--- infection
    $form->handleRequest($request);<--- infection
    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();<--- infection
        $em->persist($entity);<--- infection
        $em->flush();<--- infection
        return $this->redirect($this->generateUrl('admin_cupo'));<--- infection
    }
    return $this->render('ERPAdministracioBundle:Cupo:form.html.twig', array(
        'entity' => $entity,
        'form'   => $form->createView(),
        'form_title' => "new_cupo"
    ));
}

/**
* @param Cupo $entity
* @return \Symfony\Component\Form\Form
*/
private function createCreateForm(Cupo $entity)
{
    $form = $this->createForm(new CupoType(), $entity, array(<--- infection
    'action' => $this->generateUrl('admin_cupo_create'),
        'method' => 'POST',
    ));
    $form->add('submit', 'submit', array('label' => 'Create'));
    return $form;
}
                        

Ops' guys mastery:

Hiera file:

                            wwwUser : 'www-data'
                        
Puppet file:

    $wwwUser = hiera('wwwUser') <--------- intermediary

file { "some_dir" :
    ensure => directory,
    owner  => $wwwUser,
    group  => $wwwUser,
    mode   => 0755,
}
                        
parameters.ini

[mailer]
test_mode = True
smtp_server = smtp.server.info
port = 465
sender = sender@server.info
destination = destination_1@server.info destination_2@server.info
username = username
password = s3cr3t_password
text_subtype = 'plain'
                        
config.py <------------ intermediary

config = ConfigParser()
config.read('%s/parameters.ini' % os.path.dirname(__file__))

MAILER_SMTP_SERVER = config['mailer']['smtp_server']
MAILER_PORT = config['mailer']['port']
MAILER_SENDER = config['mailer']['sender']
MAILER_DESTINATION = config['mailer']['destination'].strip().split()
MAILER_USERNAME = config['mailer']['username']
MAILER_PASSWORD = config['mailer']['password']
MAILER_TYPE = config['mailer']['text_subtype']
                        
mailer.py

def send_mail(subject, message):
    if not config.MAILER_TEST_MODE:
        try:
            with Mail(smtp_server=config.MAILER_SMTP_SERVER, username=config.MAILER_USERNAME,
                        password=config.MAILER_PASSWORD, smtp_port=config.MAILER_PORT) as mail:
                mail.send(_to=config.MAILER_DESTINATION, _from=config.MAILER_SENDER,
                            subject=subject, message=message)
        except Exception as e:
            logger.error('Error: %s' % e)
    else:
        logger.debug('Test Mode:(Mail sended) - %s' % message)
                        

Design Pattterns

“[Design Pattern] is a general repeatable solution to a commonly occurring problem in software design.”
Design Patterns - Elements of Reusable Object-Oriented Software


“[Design Pattern] is a description or template for how to solve a problem that can be used in many different situations.”
Design Patterns - Elements of Reusable Object-Oriented Software


“Novelists and playwrights rarely design their plots from scratch. Instead, they follow patterns like "Tragically Flawed Hero" (Macbeth, Hamlet, etc.) or "The Romantic Novel" (countless romance novels).”
Design Patterns Explained Simply


“[Expert designers] reuse solutions that have worked for them in the past. When they find a good solution, they use it again and again. Such experience is part of what makes them experts.”
Design Patterns Explained Simply



  • Creational
  • Abstract Factory – Creates an instance of several families of classes
  • Builder – Separates object construction from its representation
  • Factory Method – Creates an instance of several derived classes
  • Object Poo – Avoid expensive acquisition and release of resources by recycling objects that are no longer in use
  • Prototype – A fully initialized instance to be copied or cloned
  • Singleton – A class of which only a single instance can exist
  • Structural
  • Adapter – Match interfaces of different classes
  • Bridge – Separates an object’s interface from its implementation
  • Composite – A tree structure of simple and composite objects
  • Decorator – Add responsibilities to objects dynamically
  • Facade – A single endpoint that represents an entire subsystem
  • Flyweight – A fine-grained instance used for efficient sharing
  • Private Class Data – Restricts accessor/mutator access
  • Proxy – An object representing another object
  • Behavioral
  • Chain of Responsibility – A way of passing a request between a chain of objects
  • Command – Encapsulate a command request as an object
  • Interpreter – A way to include language elements in a program
  • Iterator – Sequentially access the elements of a collection
  • Mediator – Defines simplified communication between classes
  • Memento – Capture and restore an object's internal state
  • Null Object – Designed to act as a default value of an object
  • Observer – A way of notifying change to a number of classes
  • State – Alter an object's behavior when its state changes
  • Strategy – Encapsulates an algorithm inside a class
  • Template Method – Defer the exact steps of an algorithm to a subclass
  • Visitor – Defines a new operation to a class without change

ADAPTER

orm/query.py - from SqlAlchemy library

def filter_by(self, **kwargs):
    clauses = [_entity_descriptor(self._joinpoint_zero(), key) == value
    for key, value in kwargs.items()]
    return self.filter(sql.and_(*clauses))

def first(self):
    if self._statement is not None:
        ret = list(self)[0:1]
    else:
        ret = list(self[0:1])
    if len(ret) > 0:
        return ret[0]
    else:
        return None
                        
model/adapter.py

def get_active_source(source_id):
    source = Source.query.filter_by(active=True, id=source_id).first()
    if source:
        return source.to_dict()
    else:
        return None


def get_active_source_by_country_code(country_code):
    source = Source.query.filter_by(active=True, country_code=country_code).first()
    if source:
        return source.to_dict()
    else:
        return None
                        

BRIDGE

api_v1_connector.py

API_URL = 'https://localhost:5000/api/v1/%s'

def get_source(source_id):
    res = requests.get(API_URL % 'source/%s' % source_id)
    if res.status_code == 200:
        return res.json()
    return False
                        
api_bridge.py

connector = None

def get_source(source_id):
    res = connector.get_source(source_id)
    if not res:
        raise ValueError('Error on get source with id: %s' % source_id)
    return res
                        
config.py

                            api_bridge.connector = api_v1_connector
                        
api_v2_connector.py

API_URL = 'https://localhost:5000/api/v2/%s'

def get_source(source_id):
    res = requests.get(API_URL % 'source/only-active/%s' % source_id)
    if res.status_code == 200:
        return res.json()
    return False
                        
config.py

                            api_bridge.connector = api_v2_connector
                        

PROXY

Book.php

class Book {
    private $title;
    function __construct($title) {
        $this->title  = $title;
    }
    function getTitle() {
        return $this->title;
    }
}
                        
BookList.php

class BookList {
    private $books = [];

    public function getBookCount() {
        return count($this->books);
    }

    public function addBook(Book $book) {
        $this->books[] = $book;
        return $this->getBookCount();
    }
}
                        
ProxyBookList.py

class ProxyBookList {
    private $bookList = NULL;
    //bookList is not instantiated at construct time

    function getBookCount() {
        if (NULL == $this->bookList) {
            $this->makeBookList();
        }
        return $this->bookList->getBookCount();
    }
    function addBook($book) {
        if (NULL == $this->bookList) {
            $this->makeBookList();
        }
        return $this->bookList->addBook($book);
    }

    //Create
    function makeBookList() {
        $this->bookList = new bookList();
    }
}
                        

DECORATOR

EmailBodyInterface.php

interface EmailBodyInterface {
    public function loadBody();
}
                        
Email.php - Base object

class Email implements EmailBodyInterface {
    public function loadBody() {
        echo "This is Main Email body.
"; } }
EmailDecorator.php

abstract class EmailBodyDecorator implements EmailBodyInterface {
    protected $emailBody;
    public function __construct(EmailBodyInterface $emailBody) {
        $this->emailBody = $emailBody;
    }
    abstract public function loadBody();
}
                        

class ChristmasEmailBody extends EmailBodyDecorator {
    public function loadBody() {
        echo 'This is Extra Content for Christmas
'; $this->emailBody->loadBody(); } } class NewYearEmailBody extends EmailBodyDecorator { public function loadBody() { echo 'This is Extra Content for New Year.
'; $this->emailBody->loadBody(); } }

$email = new Email();
$email = new ChristmasEmailBody($email);
$email = new NewYearEmailBody($email);
$email->loadBody();

// Output
This is Extra Content for New Year.
This is Extra Content for Christmas
This is Main Email body.
                        

FACADE


// Class to tweet on Twitter.
class CodeTwit {
    function tweet($status, $url)
    {
        var_dump('Tweeted:'.$status.' from:'.$url);
    }
}

// Class to share on Google plus.
class Googlize {
    function share($url)
    {
        var_dump('Shared on Google plus:'.$url);
    }
}

// Class to share in Reddit.
class Reddiator {
    function reddit($url, $title)
    {
        var_dump('Reddit! url:'.$url.' title:'.$title);
    }
}
                        
ShareFacade.py

// The Facade class
class ShareFacade {
    // Holds a reference to all of the classes.
    protected $twitter;
    protected $google;
    protected $reddit;

    // The objects are injected to the constructor.
    function __construct(CodeTwit $twitterObj, Googlize $gooleObj, Reddiator $redditObj)
    {
        $this->twitter = $twitterObj;
        $this->google  = $gooleObj;
        $this->reddit  = $redditObj;
    }

    // One function makes all the job of calling all the share methods
    //  that belong to all the social networks.
    function share($url,$title,$status)
    {
        $this->twitter->tweet($status, $url);
        $this->google->share($url);
        $this->reddit->reddit($url, $title);
    }
}
                        
Implementation

// Create the objects from the classes.
$twitterObj = new CodetTwit();
$gooleObj   = new Googlize();
$redditObj  = new Reddiator();

// Pass the objects to the class facade object.
$shareObj = new ShareFacade($twitterObj,$gooleObj,$redditObj);

// Call only 1 method to share your post with all the social networks.
$shareObj->share('https://myBlog.com/post-awsome',
                            'My greatest post','Read my greatest post ever.');
                        
Examples I used from:

Source Making: Structural Patterns
Design Patterns: The Decorator Pattern
The façade design pattern

some additional Cool stuff:

Anthony Ferrara - Beyond Design Patterns
Source Making: Design Patterns
Derek Banas: Design Patterns Video Tutorial


https://ignasibosch.com/talks/watertight-compartments