anúncios

quarta-feira, 1 de julho de 2026

Os conceitos de Engenharia de Software que separam Devs Juniores de Seniores

No início da carreira na programação, é comum focarmos muito em sintaxe de linguagem, frameworks e na lógica de criar pequenas funcionalidades. No entanto, conforme os sistemas crescem e passam a atender milhões de usuários, os desafios mudam de figura. O que diferencia um desenvolvedor Júnior de um desenvolvedor Sênior não é apenas o domínio do código, mas o conhecimento em System Design (Arquitetura de Sistemas Distribuídos) e a capacidade de prever falhas antes que elas aconteçam.

Abaixo, explicamos de forma didática os principais conceitos de arquitetura que você precisa dominar para elevar o nível da sua carreira.

1. Idempotência

Idempotência é a garantia de que, não importa quantas vezes você execute a mesma ação, o resultado final e os efeitos colaterais serão exatamente os mesmos da primeira execução. Um exemplo clássico ocorre em transações financeiras: se você faz um PIX e a rede oscila, o aplicativo pode tentar reenviar a requisição. Um sistema idempotente gera uma chave única (geralmente um hash baseado nos dados da transação) e bloqueia a segunda tentativa se ela acontecer dentro de um curto intervalo de tempo, evitando cobranças duplicadas.

2. Transações Distribuídas

Em sistemas monolíticos antigos, garantir que tudo funcionasse ou falhasse junto era fácil usando o banco de dados. Em arquiteturas modernas de microsserviços, surge o desafio das Transações Distribuídas. Imagine comprar um pacote de viagens: o sistema precisa reservar o voo na companhia aérea, o quarto no hotel e o carro na locadora. Se o hotel falhar, o voo precisa ser cancelado. Para resolver isso, utilizam-se padrões como o Two-Phase Commit (2PC) ou o padrão Saga (Saga Pattern), que coordena transações compensatórias para desfazer passos anteriores em caso de erro.

3. Consistência Eventual

Em sistemas globais, atualizar um dado em todos os servidores do mundo instantaneamente é impossível devido à latência da rede. A Consistência Eventual aceita que os dados fiquem desatualizados por alguns segundos ou minutos, mas garante que, eventualmente, todos os servidores convergirão para o mesmo estado correto. Um exemplo prático é o contador de visualizações de um vídeo no YouTube: você no Brasil pode ver um número ligeiramente diferente de alguém acessando na China, mas depois de um tempo os valores se igualam.

4. Réplicas de Leitura (Read Replicas)

A imensa maioria das aplicações web lida com muito mais leituras do que escritas (por exemplo, milhares de pessoas leem uma postagem de rede social, mas poucas de fato escrevem uma). Para não sobrecarregar o banco de dados principal, cria-se uma estratégia onde apenas uma instância recebe as escritas (banco de Write) e várias cópias sincronizadas lidam apenas com as consultas dos usuários (bancos de Read).

5. Teorema de CAP

Este teorema dita que um sistema distribuído só pode garantir duas de três propriedades ao mesmo tempo: Consistência (C), Disponibilidade (A) e Tolerância a Partições de Rede (P). Como falhas de rede (Partições) são inevitáveis na realidade, o Teorema de CAP na prática força o arquiteto sênior a tomar uma decisão difícil quando a rede falha: ou o sistema nega a requisição para manter os dados idênticos (priorizando Consistência) ou aceita a alteração correndo o risco de dessincronização temporária (priorizando Disponibilidade).

6. Sistemas de Mensageria e Garantias de Entrega

Ao trafegar dados de forma assíncrona por ferramentas como o Apache Kafka, existem três decisões arquiteturais sobre como as mensagens serão entregues:

  • At Most Once (No máximo uma vez): A mensagem é enviada; se falhar no caminho, é perdida. Prioriza velocidade.
  • At Least Once (Pelo menos uma vez): A mensagem é reenviada até haver confirmação. Evita perda de dados, mas pode gerar duplicatas.
  • Exactly Once (Exatamente uma vez): O sistema garante que a mensagem será processada uma única vez. É o cenário ideal, porém o mais complexo e custoso de se implementar devido ao alto processamento necessário de coordenação.

7. Backpressure (Pressão de Retorno)

Imagine um sistema raspador de dados (Web Scraper) que baixa 20 imagens por segundo (Produtor), mas o serviço de compactação dessas imagens só consegue processar 5 por segundo (Consumidor). Sem um controle, a memória ou a fila do consumidor vai estourar. Backpressure é o mecanismo de comunicação reversa que permite ao Consumidor avisar o Produtor: "Estou cheio, diminua a velocidade de envio", salvando a estabilidade da infraestrutura.

8. Thundering Herd Problem

Esse problema ocorre quando um recurso muito requisitado que estava guardado em cache expira repentinamente. No exato segundo da expiração, milhares de usuários que estavam lendo do cache tentam acessar o banco de dados principal de forma simultânea. Essa avalanche repentina de requisições pode derrubar o banco de dados instantaneamente.

9. Celebrity Problem (ou Hot Shard)

Para escalar bancos de dados, costuma-se dividi-los em pedaços menores chamados shards. Se o seu sistema armazena dados de usuários comuns divididos igualmente, o tráfego flui bem. Mas se uma celebridade gigantesca cai em um shard específico, todo o tráfego da rede social vai se concentrar naquela única máquina. Esse nó específico se torna um "Hot Shard", exigindo estratégias avançadas de distribuição para não colapsar.

10. Circuit Breaker (Disjuntor)

Inspirado nos disjuntores elétricos residenciais, este padrão monitora chamadas para serviços externos. Se uma API externa começa a falhar repetidamente, o Circuit Breaker "abre o circuito" e impede que novas requisições percam tempo tentando bater naquela API indisponível. Em vez disso, ele retorna imediatamente um erro amigável ou um caminho alternativo, protegendo os recursos internos do seu próprio sistema.

11. Feature Flags

Feature Flags são interruptores condicionais inseridos no código que permitem ativar ou desativar uma funcionalidade dinamicamente sem a necessidade de realizar um novo deploy. São extremamente úteis para realizar testes com pequenos grupos de usuários (testes beta) ou lançar atualizações de forma gradual. Contudo, desenvolvedores seniores alertam: esquecer de remover as flags antigas transforma o código em uma gambiarra difícil de manter.

12. Schema Evolution

Sistemas evoluem e as estruturas de seus dados mudam. Schema Evolution refere-se à habilidade de alterar o formato de uma tabela de banco de dados, de uma API ou de uma mensagem de evento mantendo a retrocompatibilidade. Isso garante que sistemas legados ou parceiros externos que ainda usam a estrutura antiga não quebrem quando você lançar uma atualização.

13. Migrations Conscientes

Muitos juniores cometem o erro clássico de enviar uma alteração de banco de dados (Migration) adicionando um novo campo obrigatório (Constraint NOT NULL) ao mesmo tempo em que sobem o código novo da aplicação. Durante o processo de alteração, o banco de dados pode sofrer um travamento completo (lock). Seniores planejam migrações críticas de forma faseada para mitigar riscos, utilizando passos intermediários descritos a seguir.

14. Backfill

O Backfill é o processo de preencher de forma incremental e segura dados retroativos em campos novos que foram criados no banco de dados. Em vez de travar o banco atualizando milhões de linhas de uma vez só, o desenvolvedor sênior cria o campo como opcional e roda rotinas em lotes pequenos (batches) em horários de menor movimento para popular o histórico.

15. Dual Rights (Escritas Duplas)

Estratégia usada em migrações complexas de infraestrutura onde a aplicação passa a salvar as informações simultaneamente em duas fontes de dados diferentes (o banco antigo e o banco novo). Isso permite validar a consistência e o comportamento do novo ambiente em tempo real sem desligar o sistema antigo.

16. Shadow Tables

Shadow Tables (Tabelas Espelho) funcionam como cópias ocultas que replicam as operações das tabelas principais em produção. Elas servem para testar migrações massivas em larga escala, permitindo que os desenvolvedores analisem a performance e possíveis erros de uma alteração sem impactar a experiência do usuário real.

17. Algoritmos de Rate Limit

Rate Limit é o ato de limitar quantas requisições um cliente pode fazer para proteger a API contra abusos ou ataques. Existem quatro principais maneiras de fazer isso:

  1. Fixed Window: Define uma janela rígida de tempo (ex: 100 requisições por hora). Se estourar o limite, bloqueia até a virada da hora cheia.
  2. Sliding Window: Uma janela de tempo móvel e dinâmica que avalia o histórico recente do usuário segundo a segundo, evitando abusos nas bordas do relógio.
  3. Token Bucket: Um balde virtual acumula fichas (tokens) em uma taxa constante. Cada requisição gasta uma ficha. Permite que o usuário faça rajadas rápidas de requisições se o balde estiver cheio, mas o bloqueia quando as fichas acabam até que novas caiam.
  4. Leaky Bucket: Semelhante ao balde de fichas, mas as requisições entram no balde e saem por um pequeno furo na base em uma velocidade perfeitamente constante e controlada, suavizando picos de tráfego.

18. Cache Invalidation

Existe uma famosa frase na computação que diz: "Só existem dois problemas difíceis na engenharia de software: invalidação de cache e escolher nomes para coisas". Guardar dados na memória (Cache) acelera o sistema, mas saber o momento exato de apagar esse cache quando o dado original muda no banco — para evitar que o usuário veja informações obsoletas — é um dos maiores desafios de arquitetura.

19. Cold Start

Muito comum em arquiteturas Serverless (como AWS Lambda), o Cold Start (Início Frio) é a latência ou demora que acontece quando uma função que estava desligada recebe uma requisição após muito tempo ociosa. O provedor de nuvem precisa criar uma máquina virtual do zero, baixar o código e iniciar o ambiente antes de responder ao usuário, gerando um gargalo inicial de performance.

20. Design para a Falha (Design for Failure)

Os desenvolvedores juniores programam assumindo que a rede nunca vai oscilar, que o banco de dados estará sempre online e que as APIs externas nunca vão falhar. Já os desenvolvedores seniores programam assumindo o oposto: tudo o que puder falhar, vai falhar em algum momento. O conceito de Design para a Falha dita que a arquitetura do sistema deve ser resiliente o suficiente para continuar funcionando (mesmo que de forma limitada ou degradada) quando partes dela colapsarem, utilizando mecanismos de redundância, retentativas inteligentes (com recuo exponencial) e caminhos de fallback automatizados.

Considerações finais

O maior aprendizado que diferencia os níveis de maturidade técnica é entender que não existem soluções mágicas, existem tradeoffs (compensações). Cada escolha arquitetural resolve um problema ao custo de introduzir uma nova complexidade. O papel de um desenvolvedor sênior é olhar para o cenário de negócios, analisar os prós e contras de cada conceito listado e escolher a ferramenta que melhor equilibra custo, segurança e escalabilidade.

Feito!

Nenhum comentário:

Postar um comentário