SOLIDna Architektura

Andrzej Kopeć

Czy w ogóle potrzebujemy wzorców projektowych? Zasad?

SOLID? A co to w ogóle jest?

Single Responsibility Principle

export class JSONFileConfigurationLoader {
    constructor (fsp, configFilePath) {
        this._fsp = fsp;
        this._configFilePath = configFilePath;
    }
 
    getRaw () {
        return this._fsp.readFile(this._configFilePath)
            .then(JSON.parse);
    }
 }

Open-Closed Principle

import * as _ from 'lodash';
 
export class Config {
    constructor (loaders) {
        this._config = {};
        this.readyPromise = Promise.all(loaders.map(loader => loader.getRaw()))
            .then(rawConfigs => this._init(rawConfigs))
            .then(_ => this);
    }
 
    _init (rawConfigs) {
        _.merge(this._config, ...rawConfigs);
    }
 
    get (property) {
        return _.get(this._config, property);
    }
 }

Liskov Substitution Principle

interface Uploader {
    uploadData(data: Stream|Buffer|string, type: string): Promise<string>;
    checkFile(filename: string): Promise<FileDescription>;
    getAuthenticatedUrl(filename: string): string;
}

class S3Uploader implements Uploader {}
class FilesystemUploader implements Uploader {}

Interface Segregation Principle

interface Iterable {
    Symbol.iterator: Iterator;
}

interface Collection extends Iterable {
    entries(): Iterator;
    values(): Iterator;
}

function forEach (collection: Iterable, callback: Function) {
    for (let item of collection) {
        callback(item);
    }
}

Dependency Inversion Principle

interface Uploader {
    uploadData(data: Stream|Buffer|string, type: string): Promise<string>;
    checkFile(filename: string): Promise<FileDescription>;
    getAuthenticatedUrl(filename: string): string;
}

class S3Uploader implements Uploader {}
class FilesystemUploader implements Uploader {}

function uploadData (uploader: Uploader, data: Stream|Buffer|string, type: string) {
    return uploader.checkFile(data)
        .then((fileDescription) => {
            if (!fileDescription.exists) {
                return uploader.uploadData(data, type);
            } else {
                return fileDescription.filename;
            }
        })
        .then(filename => uploader.getAuthenticatedUrl(filename));
}

Po co w ogóle o tym mówię?

Jakość kodu ma wpływ na tempo rozwoju

Parę słów o historii projektu

Nasze decyzje

  1. Zaczynamy z samym frontendem
  2. Migracja na backend
  3. Ogłupienie frontendu

Ale kod to nie wszystko...

Jeszcze potrzebna jest architektura

Luksus

Rzeczy ciężkie do zmiany

Backend

  1. Service registry
  2. Moduły
  3. Fasada per moduł
  4. Kontroler per moduł
  5. Kontroler korzysta tylko z fasady
  6. Inne moduły korzystają tylko z fasady
  7. Moduły wraz z relacjami między nimi tworzą drzewo

Frontend

  1. Komponenty
  2. Service registry
  3. SRP
  4. "Lekkie" moduły definiowane przez główne komponenty

Efekty

  1. Kod łatwy w rozwoju
  2. Architektura umożliwiająca skalowanie
  3. Architektura ułatwiająca dodawanie nowych funkcji

Pytania?

andrzej.kopec@outlook.com