O deploy faz parte do design
Se você não consegue descrever como uma mudança chega em produção com segurança, o design não terminou.
Já revisei muito documento de arquitetura. A maioria para no ponto em que o código é escrito. Caixas, setas, um banco, talvez uma fila. Depois uma linha só: 'deploy via CI/CD'. Essa linha esconde metade do risco do sistema.
O deploy não é uma preocupação separada que o DevOps resolve depois. É uma decisão de design com o mesmo peso da escolha do banco. Um sistema que não pode ser publicado com segurança é um sistema que vai ser publicado raramente, e um sistema publicado raramente acumula mudanças grandes e assustadoras. O design determinou esse resultado muito antes de alguém encostar numa pipeline.
Perguntas que o design precisa responder
Quando reviso um design hoje, faço perguntas de deploy no mesmo fôlego das perguntas de dados. Duas versões desse serviço podem rodar ao mesmo tempo? Se não, todo deploy é downtime, e o design decidiu isso por você. A mudança de schema sai antes ou depois do código que usa ela? Se a resposta é 'ao mesmo tempo', o design tem um problema de ordem escondido. Como é um deploy pela metade? Metade das instâncias na versão nova, metade na velha, compartilhando um cache com formato de chave novo. Alguém desenhou esse estado?
Essas perguntas não são detalhe operacional. Elas moldam o código. Um serviço projetado para tolerar duas versões simultâneas escreve os eventos com campo de versão, lê campos desconhecidos com tolerância e nunca renomeia uma coluna na mesma release em que para de escrever nela. Isso é escolha de design, barata no primeiro dia e cara no dia quatrocentos.
As partes com estado decidem tudo
Serviço stateless é fácil de publicar. Reinicia, volta. As partes difíceis de qualquer deploy são as que têm estado: o banco, o cache, os consumers de fila, os jobs longos, as conexões WebSocket.
Cada uma dessas precisa de um plano que faz parte do design. Para o banco, o plano é expandir e depois contrair: adiciona a coluna nova, faz backfill, troca as leituras, troca as escritas, remove a coluna velha, cada passo em sua própria release. Para consumers de fila, o plano é que mensagens novas precisam ser legíveis por consumers velhos durante todo o rollout, o que significa schema só aditivo. Para jobs longos, o plano é que o job precisa ser retomável, porque o deploy vai matar ele no meio. Para WebSockets, o plano é que o cliente reconecta com backoff e o servidor drena as conexões antes de sair.
Se o design não inclui esses planos, o time vai inventar sob pressão, e a versão inventada geralmente envolve janela de manutenção à meia-noite.
Rollback é uma feature que você constrói
'A gente só faz rollback' é a afirmação falsa mais comum no planejamento de deploy. Voltar código é fácil. Voltar uma migration que removeu uma coluna é impossível. Voltar um serviço que já publicou eventos num formato novo é um projeto de reparo de dados.
Então eu projeto para rollback explicitamente. Toda mudança ou é retrocompatível ou é dividida em passos compatíveis. A pergunta 'se a gente reverter isso em uma hora, o que quebra?' é feita na revisão, não no incidente. A resposta honesta às vezes é 'esse passo não tem volta', e tudo bem, desde que seja sabido, testado duas vezes e publicado sozinho numa manhã tranquila.
Faça a pipeline impor o design
Quando a estratégia de deploy faz parte do design, a pipeline vira a fiscalização dele. Migrations rodam num passo separado que precisa passar antes do deploy da aplicação. Health checks verificam que a versão nova consegue falar com o banco antes do tráfego chegar. Um smoke test bate nos três endpoints mais importantes. Um canary recebe uma fatia pequena do tráfego por um período fixo antes do resto seguir.
Nada disso é exótico. Tudo isso é barato quando faz parte da forma original do sistema. Os times que vejo sofrendo são os que projetaram um sistema bonito e depois tentaram publicar como um detalhe. O deploy sempre fez parte do design. Eles só não desenharam.