Contexto
A plataforma de checkout cuida de checkout, conversão, pagamentos, pedidos e autenticação para vários lojistas e lojas virtuais. É um sistema onde a arquitetura já existia e onde vários times e clientes consomem as mesmas APIs, o que faz da compatibilidade parte do trabalho, não um detalhe posterior.
Problema
- Features precisam entrar sem quebrar quem já consome a API.
- Checkout é o caminho da receita: comportamento correto importa mais que código elegante, e falhas aparecem na hora.
- Os dados cruzam fronteiras: pedidos, pagamentos e autenticação vivem em serviços diferentes, comunicando por REST e gRPC.
- Uma mudança de interface no back-end implica mudança coordenada nas aplicações que a consomem.
Minha responsabilidade
Evoluir features e endpoints existentes, criar novos endpoints quando a feature exigia (seguindo os padrões arquiteturais já estabelecidos pelo time), consumir essas APIs no front-end e escrever testes.
O que eu trabalhei
- Fluxos de checkout e conversão nas aplicações web, incluindo correções na UI de compra internacional.
- Pagamentos e pedidos: features em torno do ciclo de vida do pedido e suas representações na superfície do produto.
- Autenticação: fluxos usados pelas aplicações que consomem a plataforma.
- Endpoints: evolução dos existentes e criação de novos, consumindo-os em seguida no front-end.
- Correção de dados: migrar o front-end para campos monetários expressos em centavos, para os valores pararem de depender de representação em ponto flutuante.
- Observabilidade e testes: Sentry para problemas em produção, testes automatizados como rede de segurança para mudanças em um caminho de receita.
Como eu abordei
Antes de escrever qualquer coisa eu lia os outros endpoints do mesmo serviço e seguia as convenções deles (nomenclatura, formato de erro, validação), porque consistência é o que torna uma API aprendível. Em mudanças de contrato, eu tratava os consumidores existentes como restrição: estender em vez de redefinir, e manter o comportamento anterior válido. Como a mesma mudança costuma atravessar back-end e front-end, eu trabalhava dos dois lados do contrato em vez de entregar um formato que ninguém tinha validado. E para tudo que tocava dinheiro ou estado de pedido, os testes vinham antes de a mudança entrar.
Desafios
- Manter compatibilidade enquanto a API compartilhada por várias aplicações evolui.
- Raciocinar através das fronteiras REST e gRPC dentro de uma mesma feature.
- Debugar em um sistema onde o caminho de falha tem consequência de negócio, usando logs, traces pela stack de observabilidade e rastreamento de erros.
- Corrigir problemas de UI cuja causa raiz estava no contrato da API: o bug visível raramente estava onde a correção pertencia.
Decisões e trade-offs
- Seguir os padrões existentes em vez de introduzir um novo. Trade-off: às vezes um padrão não é o encaixe perfeito, mas um código consistente para o próximo engenheiro valia mais que uma solução localmente elegante.
- Estender contratos de forma aditiva. Trade-off: a superfície da API cresce e carrega campos legados; a alternativa era coordenar mudanças que quebram clientes.
- Dinheiro tratado como centavos inteiros de ponta a ponta. Trade-off: uma migração coordenada no front-end e um momento com duas representações em circulação, em troca de eliminar permanentemente uma classe de bugs de arredondamento.
Resultado
Features entregues em um caminho crítico de receita, e um front-end que deixou de carregar uma classe de bugs de representação monetária. A escala em volta: mais de R$3 bilhões transacionados por ano na plataforma.
O que aprendi
- Em uma API compartilhada, compatibilidade é uma feature com custo de design próprio.
- Ler o código existente antes de escrever código novo é a revisão de arquitetura mais barata que existe.
- Entender a consequência de negócio de uma falha muda o quanto de teste é suficiente.