Principios do Redux

Este é um artigo explicando a minha jornada quando implementei o redux na aplicação da Arquivei. Primeiramente vou falar sobre redux e seus princípios fundamentais e depois explicar os erros que cometemos na Arquivei e como contornamos os nossos problemas.

Se você já possui um conhecimento bem sólido sobre Redux eu recomendo pular a parte "O que é Redux?" e ir direto para "Redux no mundo real". Mas não deixe de ler as inspirações que me levou a usar essa ferramenta.

Inspirações

Quando estávamos planejando a construção da aplicação de frontend da Arquivei diversos conceitos do React e o seu ecossistema apareceram na nossa jornada tais como Flux e Redux.

Durante o planejamento da aplicação um dos programadores da equipe tinha comentado sobre a linguagem Elm e como ela era simples e semântica para desenvolver páginas web. Eu estudei um bom tanto da linguagem, segui o guia oficial e o que mais me encantou foi a promessa "No Runtime Exceptions". Isso me pareceu muito mágico, mas como já tínhamos focado em usar React + Babel (ES6 Next Generation) eu queria algo similar que não fosse atrelado à linguagem Elm. Foi assim que conhecemos a arquitetura Flux e depois o Redux.

Para mim tinha ficado claro o dever de separar completamente o que era visual do que era lógica de visualização, interação ou controle de dados. Com isso ficou óbvio que a decisão mais inteligente era usar o Redux como gerenciador de estados.

Como essa decisão foi tomada dois anos atrás é muito difícil afirmar qual o principal diferencial de termos escolhido o Redux. Eu acredito que foi a excelente documentação da biblioteca e o tutorial no Egghead ministrado pelo Dan Abramov de como implementar o redux.

Quando implementamos o redux na aplicação ficou evidente o seu funcionamento como uma máquina de estados. Recomendo que você não prossiga a leitura se você não souber o que é isso. Basta uma pesquisa no google que você achará várias leituras sobre isso, incluse no wikipedia.

O entendimento da máquina de estados como orquestrador da visualização pode parecer confuso no primeiro momento. Depois fica bem claro a rastreabilidade de inconsistências da aplicação.

Hoje não tenho dúvidas que usar o Redux na nossa aplicação foi uma excelente decisão tornando muito mais fácil a vida dos programadores e facilitando a depuração de runtime exception como e a de comportamentos indesejados.


O que é o Redux?

O Redux é uma gerenciador de container de estados para aplicações Javascript. Ele é realmente leve (2kB) e roda em diferentes ambientes (browser, mobile e servidor).

O redux ajuda a escrever aplicações bem consistentes uma vez que você separa todos os dados da sua aplicação e suas transições da UI. Além disso, torna muito fácil a vida do programador ao desenvolver funcionalidades e debugar o código revisando toda a linha do tempo das transições dos estados.

Conceitos Principais

Princípios

O Redux pode ser descrito em três princípios fundamentais. Como são princípios vocês pode fazer do jeito que você quiser e cagar na porra toda. Na Arquivei usamos os três princípios de forma bem rígida e já cometemos erro quando contrariamos esses princípios.

Única fonte de verdade

Você pode gerenciar toda a sua aplicação através de um único container de dados que pode ser serializada e hidratada na visualização sem muito esforço extra de códigos.

Uma única árvore representando dados torna a sua aplicação mais fácil de ser inspecionada e depurada. Além disso, o ciclo de desenvolvimento fica mais fácil uma vez que você consegue simular os seus dados num ambiente de desenvolvimento ou até mesmo em um ambiente de homologação controlado.

Quando erros acontecem no frontend da Arquivei nós replicamos a tela do cliente copiando o estado inicial do cliente e repetindo os eventos que o usuário efetua ao longa da aplicação.

store.dispatch({
type: 'OPEN_NFE_VISUALIZATION',
accessKey: 123
})

store.dispatch({
type: 'CLOSE_NFE_VISUALIZATION'
})

Estados apenas legíveis

O estados são apenas legíveis. Isso garante que nem uma ação de um usuário e nem a ação de uma chamada AJAX irá modificar o estado atual dos dados. Invés de mudar os dados imperativamente será necessário expor a intenção de transformar o estado através de uma ação.

Como todas as transições são bem definidas e executado em uma ordem específica, não existe uma “condição de corrida” que possa prejudicar a sua aplicação.

Como as suas ações são simples objetos elas podem ser facilmente replicados para futuros propósito de testes ou depuração. Acredite, quando bugs acontecem na Arquivei eles são facilmente depurados através desta estratégia.

console.log(store.getState())

/* Console Output
{
data: [{
accessKey: 123,
cnpj: "00.000.000/0000-00",
value: 100.00
}, {
accessKey: 124,
cnpj: "00.000.000/0001-00",
value: 200.00
}],
nfeIsOpen: false,
nfeTargetAK: -1,
}
*/

Funções puras

Para efetuar a mudança do seu estado de dados basta executar uma ação em cima do estado anterior. É basicamente isso que acontece na transformação dos estados basta lembrar de retornar novos estados invés de mudar o estado anterior.

Porque reducers são simples funções você consegue manter a ordem de qual será executada, adicionar uma informação ou até mesmo reutilizar informações anteriores.

function nfeList(state = {}, action) {
switch (action.type) {
case 'OPEN_NFE_VISUALIZATION':
return {
...state,
nfeIsOpen: true,
nfeTargetAK: action.accessKey
}
case 'CLOSE_NFE_VISUALIZATION':
return {
...state,
nfeIsOpen: true,
nfeTargetAK: -1,
}
default:
return state
}
}

Redux no mundo real

O meu propósito neste artigo é mostrar alguns erros que cometemos com o redux e melhorias que deixarão sua aplicação mais elegante e evitará escrita de código desnecessário.

Combinando reducers

Primeiramente é indicado que você combine os reducers. É recomendável que o reducer tenha um propósito específico podendo ser uma feature, uma entidade, uma especificação, etc. Isso será de acordo com o seu projeto.

O erro que cometi na Arquivei foi orientar o reducer em algumas entidades (ex: usuário, sistema) e toda a máquina de estados relacionado à visualização ficando em um único reducer que representa uma rota/página. O redutíveis da aplicações ficaram gigantescos e os códigos não ficaram nem um pouco reusáveis nas outras páginas.

Recomendo criar cada reducer para uma feature que seja reutilizável. Como exemplo das features de "notificações" e "aplicação de etiquetas" estavam no reducer da página sobre listagem de NFes. Existe uma série de outras features que estavam atreladas à listagem de NFes mas resolvi selecionar apenas duas como exemplo.

O problema é que a notificação é usada em várias outras páginas da aplicação. Em resumo separamos completamente essa feature da página e combinamos com o reducer da página. Isso deixou a feature portátil e muito mais simples de usar. O reducer da página ficou bem menor e mais objetivo.

Quanto à aplicação de etiquetas nas notas atrelamos fortemente à uma série de coisas da listagem de NFes e essa refatoração foi uma missão ardilosa. O problema é que quando tivemos que lançar essa feature para outra listagem de documentos (CTe) fazer esta refatoração foi uma obrigação.

Constantes das ações

Para facilitar o mapeamento das açoes nós criamos um arquivo com todas as ações em constantes, no caso, um arquivo de ações para cada reducer. Assim mantemos um controle das ações e ficava fácil mudar quando houvesse uma colisão de nomes, e acredite, isso aconteceu mais de uma vez. Este é um exemplo presente no guia oficial do redux.

O problema é que administrar estas constantes ficou complexo e os arquivos cresciam rapidamente. Depois de algumas tentativas de padronização acabamos descobrindo o redux-rematch. Ele não resolve o problema das constantes mas possui um padrão bem simples e consistente para as ações. Basicamente é "<reducer>/<action>", ou seja "tags/addTag".

Recomendo nunca criar um arquivo de constante e usar este padrão pois o código fica mais limpo e objetivo sem a necessidade de códigos burocráticos. Sobre o redux-rematch, quando começamos a usar a base de código encolheu cerca de 30% e deixa o código bem mais limpo.

Seletores

Uma das práticas que tinhamos era o de acessar o valores diretamente após o getState(). Isso significa que ao injetar o dado na visualização é necessário conhecer perfeitamente a estrutura dos dados. O problema é que quando o contrato com o backend mudava a mudança no nosso código se tornava um parto.

Para evitar esse problema adicionamos uma camada na arquitetura chamada Selectors. Essa camada é responsável por conhecer plenamente a estrutura e expor um dado atômico. Com isso, quando a estrutura dos dados mudava bastava mudar apenas essa interface, ou seja, nenhuma modificação acontecia na visualização.

Um exemplo do seletor está abaixo, nele uma lista com as etiquetas ativas é exposta através de uma função que recebe um estado. Se o acesso à essa lista é usada em 3 lugares diferente no código fica fácil entender o ganho com esta camada.

export const getActiveTags = state => state.tags
.data.filter(tag => tag.active)

Espero que este artigo seja útil para aqueles que estejam iniciando um projeto com redux. Para aqueles que já possuem um projeto em andamento que seja um incentivo à melhoria da aplicação.

Caso você tenha sugestões ou críticas não exite em comentar!