Boas práticas em arquiteturas de SPA e PWA

William Grasel

@willgmbr

Javascript Fatigue?

  • JQuery
  • MVC
  • OOP
  • BackboneJS
  • AngularJS
  • Ember
  • Grunt
  • Gulp
  • Webpack
  • React
  • Flux
  • Redux
  • Angular 2
  • RxJS
  • Observers
  • Streams
  • CycleJS
  • Funtional Prog
  • Reactive Prog
  • Next big thing....

Por onde eu começo??

  • HTML
  • CSS
  • JavaScript

Aprenda os conceitos por trás e além dos frameworks...

Agenda

  • Desacoplamento
  • Separando Regras de Negócio
  • Injeção de Dependência
  • Event Driven
  • Mediators
  • Component Driven
  • Data Down e Event Up
  • Componentes Burros e Espertos
  • Data Flows e Imutabilidade

Arquitetura de Software

  • MVC ?
  • MVW ?
  • MVVM ??
  • MVVMMCCDW ???

Acoplamento

  • Interface / Protocolo
  • Classe / Tipo
  • Instancia

Grau em que um módulo depende ou se interliga com outros:

Desacoplamento

  • Reaproveitamento e composição de código
  • Diminuição de efeitos colaterais em refatores
  • Mais fácil de testar de forma automatizada
  • Menos dor de cabeça e maior qualidade de vida

Cuidado...

  • Desacoplamento gera abstrações
  • Abstrações geram mais camadas de complexidade
  • Nenhuma abstração é melhor que uma abstração ruim
  • Queremos na verdade uma arquitetura evolutiva
  • O que será que realmente vale a pena desacoplar?
  • Qual é a melhor maneira de fazer isso?
  • Se apoie sobre os ombros de gigantes

Proteja suas

Regras de Negócio

  • Quais partes de sua app se manteriam, mesmo se usar outro framework ou estivesse escrevendo uma API?
  • Esse é o core da sua app, a parte mais preciosa dela
  • Desacople isso do resto da sua aplicação
  • Services, Stores, Models, Entitys, Use Cases, DDD, etc
  • Frameworks pode te ajudar, mas não ficar na sua frente...

Pense em Camadas

Injeção de Dependência

  • Melhoram a testabilidade
  • Diminuem o acoplamento
  • Promovem a Inversão de Controle
  • Prática amplamente utilizada
  • Não é a mesma coisa que módulos

Mas como?


  function CoffeeMaker() {
    var grinder = new Grinder();
    var pump = Pump.getInstance();
    var heater = app.get('Heater');

    this.brew = function() {
      grinder.grind();
      heater.on();
      pump.pump();
    };
  }

  function CoffeeMaker(
    grinder,
    pump,
    heater ) {

    this.brew = function() {
      grinder.grind();
      heater.on();
      pump.pump();
    };
  }


  function main() {
    let electricity = new Electricity();
    let grinder = new Grinder(electricity);
    let heater = new Heater(electricity);
    let pump = new Pump(heater, electricity);

    let coffeeMaker = new CoffeeMaker(grinder,
    pump, heater);

    coffeeMaker.brew();
  }

  function main() {
    let logger = new Logger();

    let electricity = new Electricity(logger);
    let grinder = new Grinder(electricity, logger);
    let heater = new Heater(electricity, logger);
    let pump = new Pump(heater, electricity, logger);

    let coffeeMaker = new CoffeeMaker(grinder, pump,
    heater, logger);

    coffeeMaker.brew();
  }




  function main() {
    let injector = new Injector();

    let coffeeMaker = injector.get(CoffeeMaker);

    coffeeMaker.brew();
  }

Diferentes Sistemas de DI


  import {Inject} from 'di/annotations';

  import {Grinder} from './grinder';
  import {Pump} from './pump';
  import {Heater} from './heater';

  @Inject(Grinder, Pump, Heater)
  export class CoffeeMaker {
    constructor(grinder, pump, heater) {
      // ...
    }
    brew() {
      console.log('Brewing a coffee...');
    }
  }

  import {Grinder} from './grinder';
  import {Pump} from './pump';
  import {Heater} from './heater';

  export class CoffeeMaker {
    constructor(
      private grinder: Grinder,
      private pump: Pump,
      private heater: Heater) {
    }

    brew() {
      console.log('Brewing a coffee...');
    }
  }

Arquitetura

Event Driven

  • Técnica de comunicação entre módulos por mensagens
  • Diferentes abordagens de comunicação por eventos
  • Pode guiar a arquitetura de toda a sua app
  • Diminui muito o acoplamento direto
  • Ainda pode restar um acoplamento indireto

Diferentes Abordagens

  • Canal único para toda app
  • Canais por tipos de dados / regras de negócio
  • Eventos de negócio através de modelos ricos
  • Cada módulo pode ter seus próprios eventos

Evite Grandes Canais de Eventos Genéricos

  • Deixam um acoplamento indireto
  • Não sabemos quem esta disparando eventos
  • Porem ainda temos uma conversa direta entre as partes
  • Através de um protocolo que ambos conhecem
  • Difícil de rastrear essa comunicação
  • Difícil de entender a ordem dos eventos
  • Difícil de escalar uma aplicação dessa forma

Mediators

  • Controla / media o relacionamento entre módulos
  • Os módulos não precisam mais conversar diretamente
  • Relacionamento entre módulos ficam explícitos
  • Assim como a ordem em que eles acontecem
  • Pode ser testado de forma automatizada

Mas como?


  class MyMediator() {
    constructor() {
      this.model = new MyModel();
      this.view1 = new View1(this.model);
      this.view2 = new View2();
    }

  class MyMediator() {
    constructor() {
      this.model = new MyModel();
      this.view1 = new View1(this.model);
      this.view2 = new View2();
    }

    start() {
      this.view1.on('my-event', () => {
        this.view2.updateSomething();
      });
    }
  }

Arquitetura

Component Driven

  • Resposta para arquitetura MVC para Front End na Web
  • Surgiu com o conceito de Web Components
  • Grande adoção por todos os frameworks js modernos
  • Nova forma de organizar modulos visuais e comportamento
  • Blocos fechados, auto-contidos, com objetivo único
  • Desacoplados, de fácil composição e reaproveitamento

Data Down Event Up

  • Padrão de comunicação entre componentes
  • Sem two way data binds mágicos
  • Sem canais de eventos globais ou genéricos
  • Dados são passados por valor aos componentes abaixo
  • Eventos são disparados para cima e podem ser ouvidos por quem estiver utilizando

Estrutura de Componentes

Dados

Eventos

Reutilizando de Componentes

Compondo Componentes

Mediator

Componentes

Burros e Espertos

  • Ou Apresentação e Container, ou Stateful e Stateless 
  • Padrão de organização de componentes
  • Aplicação do design pattern Mediator
  • Buscando maior reaproveitamento e composição
  • Separando componentes de acordo com a sua intenção

Componentes Burros

  • Apresentação dos dados e interação com o usuário
  • Recebem todos os dados que precisam diretamente
  • Não alteram os dados que receberam
  • Não sabem como mudar o estado geral da aplicação
  • Disparam eventos caso alguma coisa importante aconteça
  • Evitam estado próprio, apenas para interação do usuário

Componentes Espertos

  • Podem receber dados de entrada
  • Mas sabem como obter o resto dos dados que precisam
  • Passam os dados necessários para os componentes burros
  • Compõe a interação entre componentes burros
  • Sabem atualizar o estado da aplicação quando necessário

Fluxo de Dados

I have a dream...

Ciclo Único de Dados

Reducers


  const todosReducer = (state = [], action) => {
    const { type, payload } = action;

  const todosReducer = (state = [], action) => {
    const { type, payload } = action;

    switch(type){
      case ADD_ITEM:
        return [
          ...state,
          payload
        ];

  const todosReducer = (state = [], action) => {
    const { type, payload } = action;

    switch(type){
      case ADD_ITEM:
        return [
          ...state,
          payload
        ];
      case REMOVE_ITEM:
        return state.filter(
          item => item.id !== payload.id
        );

  const todosReducer = (state = [], action) => {
    const { type, payload } = action;

    switch(type){
      case ADD_ITEM:
        return [
          ...state,
          payload
        ];
      case REMOVE_ITEM:
        return state.filter(
          item => item.id !== payload.id
        );
      default:
        return state;
    }
  }

Referencias

Perguntas?

Obrigado! =)

@willgmbr

fb.com/wgrasel