conhecendo os 12 fatores
Assim como foi levantado no post anterior, é possível entender que escalar um sistema não é simplesmente fazer deploy de mais máquinas ou distribuir serviços sem critério. Escalabilidade de verdade exige estrutura, estratégia, planejamento e boas práticas.
Uma dessas abordagens é a metodologia twelve-factor app, ela propõe uma série de práticas para construir aplicações modernas, portáveis e resilientes, especialmente em ambientes de nuvem. Os 12 Fatores não são bala de prata, mas funcionam como um guia prático que pode ser aplicado tanto em monolitos bem construídos quanto em arquiteturas distribuídas.
Seguir os 12 fatores é parte do processo de preparação de um sistema para escala e eu recomendo que eles sejam seguidos mesmo que você não tenha milhões de usuários ainda. É uma forma de garantir que quando o crescimento vier você não vai ter que reescrever tudo do zero, comprar novos extintores de incêndio pro seu mac mini self-hosted cluster usando proxmox ou rezar pra que sua aplicação pare de ter novos clientes, recusando o dinheiro deles.
Boa parte das dores de cabeça que devs enfrentam no dia a dia (variáveis de ambiente, deploys problemáticos, logs que somem, dependências mal resolvidas, bugs irreprodutíveis…) podem ser evitadas seguindo essas boas práticas desde o começo.
Portanto, vamos passar por cada um dos 12 fatores com exemplos práticos e um olhar crítico:
O que realmente faz sentido adotar hoje? O que pode ser ignorado/adiado? Como aplicar esses conceitos em ambientes reais onde nem sempre temos a infraestrutura ideal?
A parte 1 foi sobre entender quando e por que escalar, essa aqui é sobre como se preparar para escalar direito.
1. base de código
Uma base de código com rastreamento utilizando controle de revisão, muitos deploys
Simplesmente use git, juntamente com processos de semver e git flow ou trunk based
2. dependências
Declare e isole as dependências
Use algum package manager, e por favor não apague o yarn.lock, package-lock.json ou pnpm-lock.yaml, isso vai prevenir que no futuro algum dev tenha o problema de que "na minha máquina não funciona" e que você tenha que procurar quais dependências faltam e qual versão que funciona pra sua aplicação.
3. configuração
Armazene as configurações no ambiente
Tudo que muda entre ambientes (produção, staging, local) deve estar em variáveis de ambiente, nunca hardcoded no código. Se você for um pouquinho mais cuidadoso pode utilizar algo como o AWS Secret Manager.
4. serviços de Apoio
Trate os serviços de apoio, como recursos ligados
Bancos de dados, cache (Redis), fila (RabbitMQ), tudo isso é externo e deve ser tratado como serviço, podendo ser trocado por outra alternativa e de preferência sem acoplamento na implementação. Nessa parte aqui alguns desenvolvedores se perdem em questões arquiteturais de código, achando que isso é sobre criação de abstração.
5. construa, lance, execute
Separe estritamente as builds e execute em estágios
O processo de deploy deve múltiplas etapas:
build
(compilar, instalar deps)test
(executar os testes automatizados)release
(juntar build com as configurações)run
(executar o código)
Essa separação facilita rollback e garante reprodutibilidade, cada uma das etapas pode levantar algum problema no processo e ajuda a prevenir que os sistemas de produção sejam derrubados e a aplicação fique offline sem previsão de retorno
6. processos
Execute a aplicação como um ou mais processos que não armazenam estado
Evite guardar estado local (como sessões de usuários) na memória da aplicação. Isso permite escalar horizontalmente, falamos sobre isso no post anterior
7. vínculo de porta
Exporte serviços por ligação de porta`
Seu app deve expor uma porta e rodar sozinho. Isso facilita o deploy em qualquer lugar, de Kubernetes a Heroku.
8. concorrência
Dimensione por um modelo de processo
Nesse ponto é sugerida a escalabilidade horizontal, onde escalar é como subir novas instâncias do mesmo processo. Sistemas bem desenhados deveriam poder escalar horizontalmente sem problemas, mas há controvérsias.
9. descartabilidade
Maximizar a robustez com inicialização e desligamento rápido
Sistemas devem iniciar e parar rapidamente. Isso facilita deploys, restarts e resiliência. Evite processos de shutdown demorados. Uma boa sugestão para esse fator é o uso de containers, Docker, Compose, ou outras ferramentas que repliquem ao máximo seu ambiente de produção.
10. dev/prod semelhantes
Mantenha o desenvolvimento, teste, produção o mais semelhante possível
Autoexplicativo, isso diminui a dificuldade de reproduzir os bugs.
11. logs
Trate logs como fluxo de eventos
Logs devem ser emitidos no stdout
/stderr
, nunca escritos em arquivos locais.
A coleta deve ser feita por sistemas como externos, reforçando a ideia de outros fatores citados acima.
Também seria interessante a padronização da estrutura de logs dependendo de qual será o sistema consumidor, isso facilitará em outros momentos.
12. processos de admin
Executar tarefas de administração/gerenciamento como processos pontuais
Scripts de migração, limpeza de dados e manutenção devem rodar no mesmo ambiente da aplicação, com acesso à mesma config. Podem ser agendados, utilizados como background jobs ou até mesmo serem inseridos para ser executados durante o start do container.