Foi em 2008 que trabalhei pela primeira vez na construção de um SaaS (Software-as-a-Service ou Software como Serviço), quando ainda trabalhava na RedeHost, um de meus primeiros e mais marcantes empregos como programador (5 anos na mesma empresa). De lá para cá, este se tornou meu tipo de projeto preferido, tendo trabalhado com SaaS no Route, na Umbler, projetos para clientes…e mesmo em outros projetos que não eram exatamente SaaS, eram web-apps, que guardam muitas semelhanças com SaaS também (ou seria o contrário?).
Se você está chegando agora nesse universo de programação web e não sabe exatamente o que é um SaaS, ele é um software, como o que se fazia para desktop, mas na web e utilizado por vários usuários desde que tenham conexão com a Internet, ao invés de ter um único computador com ele instalado. Assim, SaaS costumam ser aplicações web multi-tenant ou multi-inquilino: várias pessoas diferentes podem pagar taxas (geralmente mensais) para utilizar o software que você desenvolveu na web.
Falo sobre SaaS no vídeo abaixo, caso queira saber mais a respeito.
12 Factor App
Ao longo do tempo trabalhando com tais aplicações, é comum irmos acumulando bastante experiência na sua construção, bem como o que deve ser feito e o que não deve ser feito e, especificamente neste universo de SaaS, nenhuma fonte agrega tantos ensinamentos valiosos quanto o guia 12 Factor App, de Adam Wiggins (fundador da Heroku).
A Aplicação de 12 Fatores é um guia de boas práticas na construção de SaaS e web-apps em geral e reúne muito conhecimento empírico que vai evitar que você cometa erros comuns no seu projeto que possam lhe gerar problemas mais tarde. Seu objetivo é auxiliar você a ter um produto moderno, resiliente, observável, portável e escalável.
Meu intuito com este artigo não é, nem de longe, substituir o guia original que inclusive possui versão em Português ou muito menos plagiá-lo, mas sim introduzir o leitor aos 12 Fatores, trazendo ganchos práticos com o universo Node.js, que é o que ensino em meus livros e meus cursos.
Super recomendo que, após a leitura desta espécie de resenha, você se interesse em ler o original na íntegra e aplicar seus ensinamentos. Alguns você notará que soam bem óbvios na data que escrevo este post. Outros, podem soar um pouco alienígena.
Dito isso, vamos ao primeiro fator!
(se preferir, o mesmo conteúdo dos fatores pode ser visto no vídeo abaixo)
#1 – Base de Código
A base de código deve ser única e a partir dela podem ser realizados muitos deploys. Aqui a regra é clara: se você tem mais de uma base de código, você não tem um web-app, mas um sistema distribuído e cada um desses repositórios deve seguir os 12 fatores por si só.
Na prática, vejo isto em projetos Node.js se resumindo a um único repositório por web-app no Git. Atualmente fala-se muito em monorepos, onde temos vários web-apps dentro de um mesmo repositório, mas aí aplica-se a mesma regra para sistemas distribuídos, pois na prática você tem de fazer o deploy independente de cada projeto do monorepo, logo, são web-apps separados.
#2 – Dependências
Este fator diz que, todas as dependências que a sua aplicação possui devem estar configuradas para instalação junto da própria aplicação, jamais confiando que o ambiente no qual você vai fazer o deploy, tenha alguma biblioteca globalmente instalada.
Na prática, em projetos Node.js isso quer dizer que todas suas dependências de desenvolvimento e de produção devem estar devidamente referenciadas no package.json, mesmo aquelas que eventualmente poderiam ser instaladas globalmente no servidor, como TypeScript.
#3 – Configurações
Este fator diz que, todas as configurações da sua aplicação que podem variar de ambiente para ambiente (dev, hml, test, prod, etc) devem ser configuradas a nível de ambiente e jamais devem estar hard-coded dentro do seu código.
Na prática, fazemos isso em Node.js através do uso de variáveis de ambiente carregadas através de módulos como dotenv ou dotenv-safe.
#4 – Serviços de Apoio
Este fator diz que todos os serviços de apoio que sua aplicação necessita, de bancos de dados como MySQL a filas no RabbitMQ, passando por SMTP Gateway e web apis da sua própria empresa, devem ser tratados de igual forma, como recursos externos aos seu webapp.
Na prática, isto geralmente envolve ter credenciais e URLs configuradas em variáveis de ambiente para cada um destes serviços, como citado anteriormente, mesmo que o serviço em questão seja de sua autoria ou gestão. Mantenha um baixo acoplamento do seu web app com eles e será muito mais fácil fazer mudanças no futuro.
#5 – Compile, Entregue, Execute
O fator “Build, Release & Run” (no original) diz respeito aos conceitos de Continuous Integration, Continuous Delivery e Continuous Deployment muitas vezes citados no Extreme Programming.
Na prática, vejo isto em muitas empresas sendo realizado através da configuração e uso de ferramentas de DevOps como Jenkins, CircleCI, Bamboo e BitRise, que se plugam em seus repositórios Git e conforme um desenvolvedor faz um push para a master, rodam-se os testes, o build, gera-se a release com uma nova versão, muitas vezes rodam-se novos testes com a nova release e coloca-se ela em produção.
Montar este pipeline de CI/CD totalmente automatizado e ainda assim tendo a confiança de que nada vai ser estragado em produção não é uma tarefa simples, mas já vi acontecer em algumas empresas pelas quais passei.
#6 – Processos
Aplicações de 12 Fatores rodam em um ou mais processos stateless e que não compartilham dados entre si. Cada processo, quando houver mais de um, é independente e não tem ciência da existência dos demais, sendo que qualquer dado que necessite ser persistido é armazenado em um banco de dados.
Na prática, se seu web app possui um backend RESTful, você estará seguindo esta regra, pois esta arquitetura é stateless também. Da mesma forma, se estiver subindo vários processos da sua aplicação usando Node Cluster ou PM2, estará atendendo a este requisito também, pois o Event Loop estará forkado e você terá processos rodando em paralelo mesmo no caso de Node.js.
#7 – Port binding
Esse fator diz que o seu web app deve ser auto-contido (self-contained) ou seja, que não dependa de um web server como Apache ou IIS para rodar e que ele seja acessível através de uma porta local para atender requisições HTTP. Você consegue através de um comando shell subir o seu app sem ter de hospedá-lo em um web server? Então atende a este fator!
Em Node.js isso é muito simples pois é um dos princípios mais básicos da tecnologia, certo?
#8 – Concorrência
Este fator na verdade é um reforço do fator 6, uma vez que a melhor maneira de trabalhar com concorrência em web apps é tendo processos isolados, independentes e que possam ser escalados tanto na vertical (colocando mais recurso no mesmo servidor) quanto na horizontal.
As mesmas dicas que dei no fator #6 se aplicam aqui também.
#9 – Descartabilidade
Aplicações 12 Fatores podem ser derrubadas e reinicializadas a qualquer momento, sem grande ônus à experiência de uso e sem qualquer perda. Para alcançar este fator, você deve focar em ter startups rápidos e shutdowns que permitam qualquer processo pendente finalizar seu processamento antes da aplicação desligar. Mesmo no caso de falhas que derrubem sua web app inesperadamente, ela deve retomar de onde parou após subir novamente.
Este tipo de comportamento citado pode ser obtido em Node.js através de filas como RabbitMQ e AWS SQS, por exemplo. Você pega uma mensagem da fila e somente confirma ela depois que o processamento for concluído. Assim, se ele não for por algum motivo, ao reinicializar o app ele pegará novamente da fila.
#10 – Paridade entre Dev e Prod
Resumidamente, mantenha todos os ambientes o mais iguais quanto possível. Alguns dos deploys mais desastrosos que já vi foram culpa de ambientes divergentes, logo, todos os testes que fizemos antes do deploy não serviram pra nada pois o ambiente final não era igual…
Atualmente, uma técnica muito comum para resolver isso é através do uso de containers Docker, onde rodamos localmente e em demais ambientes a mesma imagem que será usada em produção, apenas com configurações diferentes (vide fator 3).
Outra técnica comum é através do uso de migrations usando algum ORM como Sequelize, o que vai garantir paridade do seu banco de dados facilmente.
#11 – Logs
Este fator não é comumente aplicado, mas deveria. Ele diz que seus logs devem ser uma stream de eventos ordenados que vão acontecendo na sua aplicação e que essa stream deve ser direcionada para o stdout (saída padrão, geralmente tela do console). Qualquer outro formato que você queira deve estar a cargo do ambiente gerenciar, plugando na sua stream algum arquivo, coletor de logs, etc.
Assim, em sua aplicação Node.js você não deveria explicitamente dizer para seus logs serem salvos em um arquivo ou sequer ter código para gerenciar este logfile, mas ao invés disso, permitir que esta saída de logs padrão possa ser capturada por algum serviço externo que vai lidar com eles.
#12 – Processos administrativos
Eventualmente você vai precisar criar scripts administrativos para fazer ajustes no banco de dados, importar dados pra dentro da aplicação, tratar dados antigos para um novo formato e assim por diante. Esses scripts administrativos, por mais que sejam utilizados somente uma vez, devem seguir diversos dos 12 Fatores como #1, #2, #3, #4, #10 e #11, para falar o mínimo.
Tecnologias como Node.js se beneficiam de ferramentas REPL para executar scripts administrativos sem necessidade de subir uma aplicação, mas, quando necessário, segue-se o fator #7 para que o script seja um app auto-contido, sem necessidade de ter um web server específico para executar ele.
E aí, o que achou destes 12 fatores? Já conhecia? Não entendeu algum? Tem outras dicas para compartilhar?
Deixe nos comentários!
Quer entender melhor o conceito de multi-tenant? Leia este artigo!
Aprenda a construir uma aplicação SaaS Multi-tenant com a stack completa do JavaScript em meu curso, clicando no banner abaixo.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.