Sabemos que operar e manter um sistema em produção não é fácil. Você pode ter tido uma ideia que deu certo. Seu produto foi bem projetado, o modelo de negócio fez sentido e o projeto vendeu, “deu em algo” e agora está em produção.
Mas e aí, qual o próximo passo? Deixar o código rodando e rezar para que tudo continue ok? Até o mês passado, sua base de usuários beirava a casa dos singelos 35. Agora entretanto, surgiram 10.500.
Antes de se dar conta, você percebe que terá que lidar com milhares, talvez milhões de requests, disparar e-mails, importar e exportar arquivos, integrar com redes sociais, ter uma página de checkout estável e o mais temido — agora você também tem que lidar com centenas de clientes que depositaram confiança na capacidade do seu sistema de entregar um produto ou serviço satisfatório. Qual o overhead de suporte que seu time ou sua empresa teriam capacidade de fornecer? E se sua equipe ao todo contiver menos de 5 integrantes?
Para fechar com chave de ouro é possível que seu sistema tenha um roadmap já planejado. Caso alguém tenha investido em seu projeto, por exemplo, é bem provável que esses interessados tenham expectativas sobre quais funcionalidades serão — e quando serão — entregues.
Essas e outras variáveis juntas são indispensáveis na hora de fazer os trade-offs, ou seja, balancear os pesos do que se deve ou não investir esforço a fim de garantir que o seu produto ou serviço escale rapidamente, seja confiável e de qualidade. Do outro lado da balança, entregar funcionalidades em um ritmo desejável a fim de manter competitividade contra produtos concorrentes também é de vital importância.
Portanto, neste artigo vou te dar 15 dicas que servem na maioria das situações para que você e seu time consigam operar sem dor em produção e, principalmente, evitar que um projeto bem sucedido morra na praia.
1. Ter ao menos um ambiente de testes devidamente isolado, coerente e próximo em funcionalidades
Manter um sistema que ao menos receba pagamentos ou que possua funcionalidades básicas que dependem de jobs, cronjobs, integrações e filas é uma tarefa árdua quando não há ao menos um ambiente seguro para que essas tarefas aconteçam sem interferir nos dados da vida real. Portanto, ter um ambiente de homologação ou staging minimamente “verossímil” é fundamental.
Motivos não faltam. Nem tudo que funciona em localhost:3000 funcionará no seu sistema com domínio próprio rodando em Kubernetes, Heroku, AWS, GoDaddy ou qualquer que seja — e isso é apenas 1% da ponta do iceberg. Muitas integrações não funcionam por natureza em ambiente local.
Num mundo ideal, semelhante ao que ocorre com projetos que já possuem um nível de maturidade alto, é comum inclusive a existência de níveis diferentes de staging, cada um com um grau de fidelidade. Um deles com a finalidade de realizar testes rápidos, mockando o que for tolerável a fim de poupar tempo. O outro ambiente, por sua vez, faria disparos reais de e-mail, integrando com ambientes de homologação com parceiros externos para manter a testabilidade de toda a jornada do sistema.
Para um time com recursos limitados, a sugestão é criar ao menos o ambiente mais próximo do real, para os testes com ambientes sandbox dos meios de pagamento, disparos reais de e-mail, integrações no ambiente de homologação dos parceiros, sempre recebendo somente dados que façam sentido.
2. Suíte de testes automatizados
Esta é talvez a mais óbvia, porém aquela cuja ausência causa mais problemas no futuro. É necessário ter uma suíte de testes automatizados que ao menos faça o double-check das regras de negócio do seu sistema e que possa, inclusive, explicar casos de uso e comportamentos de forma mais fácil e de simples manutenção.
Acima de tudo, uma suíte de testes automatizados também serve como um porto seguro para garantir a testabilidade de regressão do seu sistema. Nada garante que o merge de um código que tenha contido conflitos não possa retirar por acidente uma lógica que havia sido definida no passado.
Se pararmos para pensar, os maiores problemas em aplicações em produção ocorrem não porque uma animação para de funcionar — algo coberto em testes ao estilo Selenium. Em vez disso, os problemas mais sérios costumam ocorrer quando regras de negócio são ignoradas, modificadas sem controle ou mal-interpretadas, gerando efeitos colaterais envolvendo valores monetários, permissionamento indevido e outras falhas que podem abalar gravemente a operação da sua empresa.
3. Faça o logging de tudo o que for preciso, desde que não seja informação sensível
Logar informações é sempre importante. Quando um sistema já está em produção, é fundamental para que bugs sejam resolvidos rapidamente. A dica aqui é logar de forma inteligente. Quase toda plataforma de IaaS (Infrastructure as a Service) — entre elas GCP, Amazon AWS, Azure e outras — possuem algum sistema de agregação de logs.
O cuidado tem que ser sempre em obscurecer informações sensíveis. Claro que senhas e derivados já não deveriam nunca estar em texto plano por natureza. Porém, ainda existe a chance de expor acidentalmente dados sobre seus usuários que não deveriam ficar em qualquer log por aí. Imagens de conversas privadas são um exemplo clássico de algo que deveria ser obscurecido em um log.
4. Não atropelar o essencial para entregar mais rápido
Pode até parecer tudo bem modelar algo no seu sistema e depois ter que voltar atrás logo no começo, porém, se você já tem uma base de clientes ou usuários em produção, esse tipo de fenômeno deve passar a ser exceção. Ninguém vai gostar de entrar no seu sistema caso ele passe um dia inteiro fora do ar, “voltando no tempo” antes de um erro de modelagem ou de uma perda de dados causada acidentalmente.
Portanto, deve haver, no mínimo, uma parte do seu processo de desenvolvimento que conte com um período de validação em staging, principalmente do que é mais sensível em termos de regras de negócio. Não esqueça que um tempo suficiente para projetar e modelar uma nova funcionalidade também é fundamental, para que não sejam feitos “puxadinhos” dos quais você com certeza vai se arrepender no futuro.
5. Tenha um roadmap
Pode se tornar complicado evoluir um produto que já está em produção com um time pequeno. Afinal, seu time agora também tem que eventualmente tratar incidentes e talvez fornecer suporte técnico. Isso é ainda mais difícil se você não tiver um plano.
Recomendo que seja feito sempre um roadmap — através de listas com bullets, mapas mentais ou outra forma semelhante — para ao menos mapear o que será desenvolvido nos próximos 2, 3 e até 6 meses de projeto.
Aprendemos com a experiência aqui na Ateliware que desenvolver olhando para o escuro é a receita perfeita para um sistema que pode ficar estagnado no tempo ou perder a chance que tinha de conquistar um grande mercado. Logo, tenha sempre um objetivo, mesmo que resumido, e itere sobre esse objetivo ao longo do tempo.
6. Cuide dos custos acumulados de decisões técnicas, refatorações e atualizações de bibliotecas com frequência
Não é preciso desenvolver no modo go-horse para deixar para trás algum custo acumulado de decisões técnicas que precisam ser corrigidas. É natural que mesmo um sistema desenvolvido com planejamento tenha suas refatorações ainda não feitas, que por conta do ritmo rápido de crescimento tiveram de ser toleradas e postergadas.
Entretanto, é recomendado que se itere sobre tais débitos, como códigos repetidos, a fim de evitar que em um futuro próximo sua base de código seja insustentável de manter.
Do ponto de vista das bibliotecas e frameworks, atualizá-los sempre e pouco a pouco também é parte de um processo constante. Pare para pensar: até que é rápido atualizar a versão do seu projeto em Ruby on Rails da versão 5.2 para a 6, não? Porém, ao fazer o mesmo processo da versão 3 para a 6, em um sistema que já existe em produção, isso poderia levar meses.
A dica é atualizar sempre para atualizar facilmente. Tire um tempo do projeto, em um intervalo que pode ser de 2 em 2 meses, dedicado a iterar sobre as dependências das bibliotecas, versões de frameworks e linguagens. E, claro, teste isso tudo no staging antes de subir para produção.
7. Respeite os dados de seus usuários
Um debate constante tem se intensificado, principalmente no Brasil, sobre as formas corretas de se lidar com dados dos usuários. Com a LGPD (Lei Geral da Proteção de Dados), esse cuidado se tornou ainda mais importante e fundamental para que qualquer sistema em produção tenha uma política clara sobre a retenção de dados dos usuários, e até recursos para limpeza, exportação ou remoção desses dados a pedido do cliente.
Vale lembrar que guardar senhas e outras informações sensíveis — como número de cartões de crédito — em texto plano nunca foi aceitável! Prefira cartões tokenizados, se for guardar, e senhas somente em hash.
8. Pace e deploy constante
Para que seu time mantenha as entregas e um ritmo de deploy confiável e constante é fundamental que se otimize esse processo. Portanto, é importante confiar em ferramentas de Continuous Delivery/Continuous Deployment (CD) e Continuous Integration (CI).
No caso do CI, a principal função é fazer com que o código sempre seja re-testado e re-integrado com a funcionalidade que já estava lá anteriormente. É possível, por exemplo, fazer com que seu container rode a suíte de testes sempre que um pull request for aberto. Ferramentas como o GitHub têm integrações disponíveis para travar o merge do pull request em caso de reprovação automaticamente.
No caso do CD, a ideia é focar na facilidade de deployar algo novo. Ter um processo de deploy que dura 2 dias faz com que você e seu time percam muito mais tempo com o processo em si do que com o próprio desenvolvimento. Assim que um código entrar na branch destino, é possível fazer com que esse código tenha seu deploy facilitado e em grande parte automatizado, garantindo que pequenas features vão entrar sempre que algo novo estiver pronto.
9. Tenha ferramentas para prover certas funcionalidades de forma assíncrona
É quase impossível que um sistema grande em produção não dependa de nenhuma estrutura de organização de tarefas assíncronas. O próprio disparo de e-mails, por exemplo, já pode se tornar um gargalo caso a aplicação cresça e tenha de ser feito em tempo real. Importações, exportações, machine learning, cron jobs e muitos outros processos são exemplos claros de que seu sistema em produção teria que consumir muito mais recursos para dar conta de tudo isso em tempo real.
Exemplos não faltam em qualquer linguagem: Sidekiq, RabbitMQ, quaisquer libs que utilizam Redis ou Memcached por baixo para gestão mais elaborada de filas e jobs assíncronos ajudam nesse aspecto. Escolha sempre as ferramentas mais bem avaliadas e mais focadas naquilo que seu projeto precisa.
10. Audits, versionamento e soft delete dos dados
Essas 3 atividades se completam e são indispensáveis para fornecer a rastreabilidade de tudo o que é feito no seu sistema em produção.
Auditioning é a ideia de gerar uma espécie de log permanente para cada modificação relevante que é feita em uma entidade no sistema. A ideia é que audits não devem ser deletados depois de um tempo, como ocorre geralmente com logs comuns. Se alguém der algum acesso importante a outro usuário ou alterar algum valor que tenha impacto relevante na sua operação, é fundamental deixar esse rastro.
Versionamento das entidades é a ideia de criar uma cópia das entidades do seu sistema toda vez que ela for modificada. Isso pode economizar muito tempo correndo atrás de logs. Como temos trabalhado bastante com Ruby on Rails, um exemplo rápido de biblioteca capaz de lidar com isso seria o Papertrail. Mas bibliotecas como essas são bem diversas e presentes em todas as linguagens.
11. Bancos de dados e outros serviços autogerenciáveis
Pode ser mais barato à primeira vista ter um banco de dados hospedado em uma máquina física ou em um container simples. Porém, as rotinas de backups e otimizações, bem como prevenção de falhas, multi-sharding, dynamic pools e outras funções podem se tornar um pesadelo caso seu time não seja grande.
Portanto, a dica é usar algum serviço mais autogerenciável, como Google Cloud SQL, Amazon RDS, Heroku DB Addons e Azure Database, especialmente se os recursos do seu time forem limitados. Esses serviços têm programas de backups automáticos diários e outros benefícios que previnem muita dor de cabeça. O investimento financeiro, apesar de maior, tem muito mais retorno — afinal, às vezes o barato sai caro.
12. Ambiente de deploy flexível e conteinerizado, sem downtime
Com a conteinerização, você pode reproduzir o mesmo ambiente em qualquer lugar, quantas vezes for necessário, com o mesmo comportamento. Além disso, um ambiente de deploy flexível deve possibilitar que sua topologia — a estrutura do seu cluster de serviços — seja reproduzida facilmente caso um dia seja necessário trocar de ambiente.
Para um sistema com muitos usuários em produção, os cinco minutos necessários para subir seu deploy podem ser bem longos para que seu serviço fique offline. Portanto, é fundamental deployar sem downtime. Isso é possível em quase todas as ferramentas de orquestração, como o Kubernetes, que por meio de um conceito conhecido como awareness probe consegue saber quais réplicas do seu sistema já estão preparadas para atender requests e quais não estão.
13. Analytics, monitoramento, BI, SEO
Para que seu sistema tenha um futuro promissor, é fundamental que essas quatro áreas estejam presentes em produção.
Analytics servem principalmente para que você tenha informações sobre o comportamento dos usuários. Será que o seu cliente final consegue chegar ao final do checkout com facilidade? Qual o tempo médio que o usuário passa utilizando seu aplicativo?
Monitoramento (APM — Application Performance Monitoring) permite que você entenda quais são os gargalos do seu sistema. Exemplos de soluções bem conhecidas de APM: DataDog, New Relic, entre outros.
BI (Business Intelligence): é importante que a equipe de gestão tenha a visão do que está acontecendo. Seu projeto está trazendo resultados financeiros? Quantos usuários o sistema ganha por semana? Esse é o tipo de pergunta que você pode responder com um dashboard de BI. Mas, não se esqueça: acessos somente para leitura.
SEO (Search Engine Optimization): se o seu sistema compete com concorrentes em produção é provável que você queira aparecer antes deles no Google. Siga no mínimo algumas diretivas básicas de SEO. Ferramentas como o WebPageTest, que rodam inclusive a engine do Google Lighthouse, são bem úteis na hora de entender se o seu sistema é otimizado para dispositivos móveis, conexões lentas e SEO em geral.
14. Seu código também precisa ser escalável
Ter estruturas assíncronas, ambientes escaláveis e até um CDN para distribuição de assets é realmente muito importante. Mas e o código? Ouço muitas pessoas dizerem que não utilizam Rails, por exemplo, porque “não é escalável”. Se ferramentas gigantes e com milhões de usuários como o GitHub são escaláveis, como fica essa história do que é ou não é escalável?
De certa forma, código ruim quase nunca é escalável. Não adianta ter um serviço desenvolvido na linguagem mais rápida possível se após fazer uma query você iterar por essa lista de resultados 5 vezes diferentes, cada uma utilizando uma função map diferente.
Por trás de operações como as do Facebook estão caches, CDN, multi-sharding, indexações complexas, sistemas de pool virtuais nos bancos e muitos outros mecanismos. Linguagem rápida é uma parte da equação, não a solução inteira.
15. Esperar que algumas coisas quebrem
A última dica é mais relacionada ao aspecto psicológico da equipe do que qualquer outra coisa, pois vez ou outra, alguma coisa em produção certamente vai quebrar. Ainda não há APM, planejamento ou suíte de testes capaz de evitar que absolutamente todos os bugs sejam prevenidos.
A dica consiste em 2 partes. A primeira é tentar fazer com que os bugs não afetem tanto as lógicas de negócio, o que vai de encontro à filosofia de ter testes automatizados que cobrem de forma bem rígida tais cumprimentos de regras. Se for para investir mais tempo garantindo que não haja falhas, que esse tempo seja dedicado às regras de negócio mais críticas à operação.
A segunda parte consiste em trabalhar a maturidade da equipe a fim de entender que quebras em produção são riscos inerentes ao processo. Bugs e incidentes críticos devem sempre ser tratados com urgência maior do que a sprint convencional e, eventualmente, até dedicar ou rotacionar parte da equipe para ser a linha de frente responsável por lidar com incidentes.



