A Microsoft acabou de prometer que o próximo Xbox rodará todos os jogos de Xbox já lançados. Ao mesmo tempo, a Microsoft está trazendo títulos de Xbox 360 para PC. Retrocompatibilidade agora é promessa de marca, não rodapé. Se um dispositivo de consumo consegue manter um catálogo de software que atravessa décadas, seu SaaS não tem desculpa para quebrar um cliente v1 porque um PM quis um nome de campo mais “limpo”.
Retrocompatibilidade é um fosso competitivo. Ela evita churn em contratos enterprise, impede parceiros de criarem silenciosamente um Plano B e poupa seu suporte de “workarounds” pírricos. Isso não vem de boas intenções. Vem de política, telemetria e suítes de teste que punem mudanças incompatíveis do mesmo jeito que um pipeline de CI pune unit tests falhando.
O caso de negócio: compatibilidade é lucro, não caridade
Nos produtos B2B que escalamos na DHD Tech, 12–20% da receita envolve ao menos uma integração “sticky” (difícil de trocar): um ERP legado, um app mobile que atualiza devagar ou um SDK de parceiro embarcado em dezenas de implantações em clientes. Essas também são suas contas de maior margem. Se você as quebra, duas coisas acontecem: seu TAM fica menor (parceiros deixam de integrar) e seu CAC aumenta (escalations de suporte e retrabalho de reimplementação). Compatibilidade não é “nice-to-have”; é estratégia de proteção de margem.
Realidade sobre o comportamento de atualização de clientes:
- Cauda longa no mobile: Mesmo com updates forçados, 10–20% dos usuários ativos no mês podem estar 6+ meses atrasados; 2–5% ficarão 12–18 meses atrás (apps de viagem e delivery tendem a envelhecer; fintech tende a estar mais atualizada).
- SDKs embarcados: Em pilhas B2B de parceiros, ciclos de upgrade de 12–24 meses são comuns; alguns clientes adiam upgrades até a troca de hardware.
- APIs em verticais regulados: Compradores em banking e healthcare negociam janelas de descontinuação em MSAs — 12–36 meses é rotina.
Se você mantém compatibilidade de forma deliberada, absorve 3–5% de custo extra de engenharia e evita 10–15% de risco de receita justamente nas contas que o mantêm vivo em um downcycle.
Decida seu horizonte de compatibilidade por escrito
Escolha um número e coloque-o em seus contratos e docs. Sem enrolação.
- 3 anos: B2C de consumo com dependências mínimas de parceiros; tolerante a risco.
- 5 anos: SaaS horizontal com integrações e SDKs de parceiros; padrão para a maioria das startups que vendem para SMB + midmarket.
- 10 anos: Fintech, healthcare, logística, plataformas para desenvolvedores; qualquer coisa com integrações de terceiros que se tornam críticas para o cliente.
Seu horizonte dita cobertura de testes, shims no gateway e orçamentos de deprecação. Se você não decidir isso, vai improvisar sob fogo depois.
Faça o inventário dos vetores de quebra
Catalogue o que pode quebrar e quem paga quando quebrar:
- APIs públicas REST/gRPC usadas por parceiros
- APIs internas consumidas por seus apps web/mobile
- Streams de eventos (Kafka/Kinesis/PubSub) consumidos por clientes ou parceiros
- SDKs mobile e de servidor embarcados em pilhas de clientes
Etiquete cada um com: número de consumidores ativos, criticidade (P0–P3), esquema de versionamento atual e data da última mudança incompatível. Se você não consegue preencher isso, não controla sua própria superfície.
Estratégia de versionamento por interface
REST/JSON
- Prefira evolução aditiva. Nunca reaproveite um campo para outro fim. Só adicione campos; nunca remova sem um shim.
- Versione no path (/v1/orders) ou no media type. Versionar apenas via headers dificulta telemetria e suporte.
- Enumerações: Sempre aceite valores de enum desconhecidos e ignore-os. Seu cliente v1 deve sobreviver quando a v3 adicionar um novo status.
- Números e dinheiro: Não mude centavos inteiros (integers) para moeda em ponto flutuante. Se precisar, adicione um novo campo e deixe o antigo intocado.
- Datas: Fique no RFC 3339 com fuso horário. Horários locais ambíguos vão te morder em fronteiras de DST.
gRPC/Protobuf
- Nunca reutilize números de campo. Marque campos removidos como reserved para sempre.
- Não mude tipos (int32 para string) nem obrigatoriedade. Adicione novos campos opcionais; clientes antigos devem ignorar campos desconhecidos.
- Expansões de oneof são ok; mudar a semântica não é.
- Cuidado com defaults. Protobuf v3 trata campos ausentes e com valor default de forma similar; deixe a lógica de negócio explícita.
Eventos e streams
- Envelopes auto-descritivos com id e versão de schema são inegociáveis.
- Registries de schema (Avro/Protobuf/JSON Schema) com modos de compatibilidade configurados para BACKWARD (ou FULL) bloqueiam deploys de produtores.
- Contratos de evento imutáveis: nunca mude o significado de um campo. Adicione novos eventos para mudanças semânticas.
GraphQL
- Aditivo é seguro; remoção é quebra. Use
@deprecatedreligiosamente e mantenha deprecações até o uso cair abaixo do seu limiar por N semanas. - Mudanças no resolver padrão são quebra se alterarem nulabilidade ou faixas de valor. Trate-as como tal.
Shims: onde colocar a camada de tradução
Você tem três lugares para lidar com clientes antigos:
- Manter o código do serviço antigo (v1 continua vivo). Simples, mas você paga para corrigir bugs para sempre e carrega lógica duplicada.
- Tradução na borda (gateway). Envoy/Kong/NGINX + transforms em WASM/Lua/JS mapeiam requisições v1 para v3 e respostas v3 de volta para v1. A sobrecarga típica é de 0,5–2 ms por salto, muito mais barata do que manter caminhos legados no código.
- Adaptadores no cliente (SDKs). Arriscado se você não controla todos os clientes; funciona melhor para seus próprios apps mobile/web.
Nossa regra prática: use tradução na borda para parceiros externos, adaptadores no cliente para seus apps e mantenha código legado apenas como ponte com tempo definido (90–180 dias) enquanto constrói transforms de verdade.
Torne a quebra visível: SLOs de compatibilidade
Se você não mede compatibilidade, você não a tem. Defina SLOs que forcem ação:
- Taxa de erro de compatibilidade (por versão): 4xx por incompatibilidades de schema/roteamento para vN-1 e vN-2 fica abaixo de 0,1% das requisições ao longo de 7 dias.
- Aceitação de campos: a taxa de campos desconhecidos permanece abaixo de 1% por 30 dias após o lançamento de um novo campo (indica evolução aditiva segura).
- Orçamento de diff em shadow: diffs de resposta entre caminhos de código antigo e novo abaixo de 0,5% para tráfego espelhado antes do cutover.
Marque toda requisição com dimensões de client-version e api-version. Toque no limite de cardinalidade aplicando hash em versões além dos dois primeiros segmentos (ex.: 5.12.x). Se seus logs não permitem fatiar por versão, você está dirigindo sem velocímetro.
Construa uma suíte de compatibilidade que rode no CI
Comece simples; itere sem parar:
- Corpus de payloads de referência (golden): Capture e anonimize 500–1.000 requisições reais por recurso principal (cubra o top 90% do tráfego vivo). Armazene-as com respostas esperadas para vN-1 e vN-2. Reexecute em todo PR e deploy.
- Testes de contrato: Use checks de OpenAPI/Protobuf/GraphQL para bloquear mudanças incompatíveis. Para APIs de parceiros, adicione contratos dirigidos pelo consumidor (p.ex., Pact) para seus 10 principais consumidores.
- Tráfego espelhado: Espelhe 1–5% do tráfego de produção para a nova implementação. Faça diff das respostas (listas de permissão de cabeçalhos, tolerâncias de valores). Ferramentas como Envoy Tap, comparadores ao estilo Diffy ou um sidecar caseiro funcionam bem.
- Gates no registry de schema: Para eventos, imponha compatibilidade na etapa de build do produtor e no CI.
Espere que a suíte inicial custe 2–3 meses-engenheiro para subir e ~0,5 FTE para manter. Em escala, ela se paga ao capturar regressões muito antes dos clientes.
Deprecação é um orçamento, não um post de blog
Escolha uma política de deprecação que respeite seu horizonte e imponha-a de forma consistente:
- Sinais: exiba headers de deprecação nas respostas, logs/eventos estruturados para suas ferramentas de CSM e avisos no console admin para clientes.
- Janelas: B2C mobile, 180–270 dias; B2B SaaS, 12–18 meses; clientes regulados/SDK, 24–36 meses. Coloque a janela em suas MSAs.
- Gates: Não remova até o uso ficar abaixo de 0,5% das requisições por 8 semanas, ou até existirem shims escritos e testados.
- Exceções: extensões com um clique para contas top, limitadas a 90 dias com aprovação explícita do CTO.
A cadência de comunicação importa: lembretes em 90/60/30/14/7/1 dia por email e banners in-product. Forneça trechos de código para os novos endpoints, não apenas links para docs.
SDKs e mobile: reduza a pressão de quebras binárias
SDKs multiplicam seu raio de explosão. Torne-os simples e resilientes:
- Empurre a compatibilidade para o wire: mantenha SDKs finos; a maior parte da lógica no servidor, de modo que shims no servidor protejam SDKs antigos.
- Descoberta dinâmica: busque as capacidades de API suportadas na inicialização; não fixe feature flags no binário.
- Faixas de SO e runtimes: assuma uma versão mínima realista de SO e cumpra; mudar o mínimo de iOS/Android quebra empresas com restrições de dispositivo.
- Kill switches: flags de remote-config para desabilitar features recém-incompatíveis sem enviar um novo build do app.
Economia de engenharia: quanto isso realmente custa
Disciplina de compatibilidade não é grátis. Orce isso como um recurso central de confiabilidade:
- Camada de tradução: 0,25 FTE por versão legada ativamente suportada (transforms na borda + testes).
- Manutenção da suíte: 0,5 FTE contínuo para manter payloads de referência atualizados e diffs de shadow precisos.
- Telemetria + dashboards: 2–3 semanas para adicionar marcação de versão, controle de cardinalidade e painéis de SLO.
- Produto + Success: 2–4 horas/conta para comunicações de deprecação e extensões em clientes enterprise.
Vimos esse investimento reduzir chamados de quebra em 30–50% e economizar 0,5–1,5 pontos de margem bruta em escala, eliminando retrabalho emergencial e descontos em renovações.
Armadilhas comuns (e como evitá-las)
- “É só um rename.” Isso não existe. Adicione um alias, mantenha o campo antigo e emita ambos até o uso cair.
- Float para dinheiro: erros de arredondamento viram inferno de reconciliação. Mantenha unidades inteiras menores para clientes antigos; adicione strings decimais para os novos.
- Fusos horários e DST: Aceite offsets, armazene em UTC, retorne RFC 3339 com informação de fuso. Não infira horários locais do usuário no servidor.
- Nil vs ausente: Em Protobuf v3 e JSON, ausente e null se comportam diferente entre linguagens. Deixe a semântica explícita e teste nos dois sentidos.
- Chaves de idempotência: Mudar comportamento de idempotência (TTL, escopo) é uma mudança incompatível. Versione isso ou mantenha a semântica anterior em um shim.
- Nullability em GraphQL: Apertar nulabilidade é quebra. Use deprecação ao nível de campo + novo campo non-null; não mude in place.
Um plano de rollout de 90 dias
Dias 1–30: Visibilidade primeiro
- Adicione marcação de api-version e client-version a toda requisição/resposta. Publique painéis de volume e taxa de erro por versão.
- Publique seu horizonte de compatibilidade (3/5/10 anos) internamente; inclua em docs e novos contratos.
- Congele mudanças incompatíveis até existirem SLOs e testes.
Dias 31–60: Gates e suíte
- Levante checagens de contrato no CI (OpenAPI/Protobuf/GraphQL).
- Construa seu primeiro corpus de payloads de referência para os cinco endpoints principais e um teste básico de replay.
- Habilite transforms em Envoy/Kong ou equivalente na borda; migre uma mudança incompatível trivial por trás disso para endurecer o caminho.
Dias 61–90: Músculo de deprecação
- Defina janelas de deprecação por segmento (B2C/B2B/regulado) e incorpore-as às MSAs.
- Inicie seu primeiro experimento de tráfego espelhado (1%) para uma migração de vN para vN+1; crie um orçamento de diff de resposta.
- Treine suporte e CSMs na cadência de comunicação e no processo de exceção. Faça extensões com aprovação do CTO.
A mudança de mindset
Retrocompatibilidade não significa nunca mudar. Significa mudar de forma deliberada. Se a Microsoft consegue carregar jogos através de gerações de chips, você consegue manter viva a integração de um parceiro com seis anos enquanto lança a v4. Você só precisa tratar compatibilidade como latência ou uptime: uma propriedade objetiva, medida, com orçamentos e enforcement — não uma promessa que você espera que os engenheiros lembrem.
Pontos-chave
- Escolha um horizonte de compatibilidade de 3/5/10 anos e publique-o. Sem horizonte, sem plano.
- Empurre mudanças incompatíveis para shims na borda; mantenha código legado apenas como ponte com tempo definido.
- Imponha SLOs de compatibilidade: taxas de erro por versão, orçamentos de diff em shadow e gates de schema.
- Levante uma suíte de compatibilidade: payloads de referência (golden), contratos dirigidos pelo consumidor e 1–5% de tráfego espelhado.
- Deprecação é um orçamento com limiares e janelas, não um post de blog.
- Mantenha SDKs finos e resilientes; jogue a complexidade para o wire, onde shims podem proteger clientes antigos.
- Espere 3–5% de custo de engenharia; evite 10–15% de risco de receita nas suas contas mais “sticky”.
Author: Diogo Hudson Dias