A Extração de Assinaturas: Uma Ordem de Leitura

Ao longo de cerca de um mês escrevi dezessete posts sobre um único projeto. Foram escritos conforme o trabalho acontecia, o que significa que cada um começa de onde as coisas tinham chegado em vez de começar do início.

Este é o início.

A premissa: um app de esportes com um tier pago. Usuários assinam pela App Store da Apple, pelo Google Play, ou por Stripe na web, e uma assinatura bem-sucedida concede uma role (internamente PRO) que libera as features pagas. Tudo isso vivia num único monolito Django: os endpoints de compra que o app mobile chama, os endpoints de webhook que as lojas chamam, o catálogo do que é comprável, e a lógica que decide se uma compra dá direito ao PRO.

Extraímos a metade de assinaturas para um novo serviço em Go, e tomamos a decisão mais consequente antes de qualquer código: os dados não se movem. O serviço Go conecta no cluster Postgres existente do monolito e escreve as mesmas tabelas. Nenhum banco novo, nenhuma migração, nenhuma camada de dual-write.

Isso foi julgado o risco menor, e o custo é que por toda a transição duas implementações escritas independentemente escrevem as mesmas linhas. O que muda a definição de pronto. Uma reescrita tem sucesso quando funciona; um port nessa posição só tem sucesso quando concorda. Quase tudo abaixo decorre dessa única restrição.

Medir e dimensionar

  • Quantifique a Falha Antes de Redesenhá-la: "assinar não é confiável" era a premissa do projeto inteiro. Medida no log group de produção, a falha não era instabilidade: era uma máquina de estados rejeitando compras que deveria ter aceitado, 3.995 vezes em sete dias. Comece aqui.
  • Inventariando Toda Superfície Que um Monolito Expõe: você não pode reconstruir o que ninguém listou. Derivar as promessas do código em vez da memória, e transformar o inventário em algo que testes impõem.
  • Quem Mais Mexe Neste Banco de Dados?: a pergunta bloqueante de qualquer extração. Todo acoplamento com direção e evidência anexadas, mais a descoberta de que entidades chaveadas por um único provedor silenciosamente tornam seu histórico dependente de um fornecedor.

Desenhar e comprometer

  • Uma Arquitetura Especificada em Números, Não em Adjetivos: "escalável e confiável" é um desejo com boa assessoria de imprensa. Escrever o alvo como mecanismos e orçamentos, e dar a todo modo de falha conhecido um sinal consultável antes de construir qualquer coisa.
  • Um Cutover Que Pode Ser Revertido: reversibilidade como critério de aceitação em vez de um plano de rollback escrito na véspera, e por que "podemos reverter o deploy" deixa de ser verdade no momento em que dados se movem.
  • Uma Conexão de Banco Que Não Pode Machucar o Monolito: durante a fase de sombra o novo serviço lê o sistema que serve todo usuário, então seus critérios são expressos como capacidades que ele deve não ter.
  • Tracing: Instrumentado mas Silencioso: instrumentado com OpenTelemetry no primeiro dia e configurado para não emitir nada, porque nenhum coletor tinha sido escolhido. Separar uma decisão de código de uma decisão operacional.

Provar

  • Provando uma Reescrita Contra 243.325 Compras Reais: antes de responder a um único usuário, o novo serviço calculou entitlement para toda compra que tínhamos, comparado offline com as respostas do monolito. Zero discordâncias; a engenharia está inteiramente no que "zero" exigiu.
  • Cobertura, Não Apenas Concordância: um harness de paridade reportando 100% de concordância não diz nada até você saber o que ele perguntou. Transformar cobertura num portão em vez de uma estatística.
  • Uma Loja Nunca Espera Pelo Nosso Banco de Dados: as lojas retentam agressivamente quando você é lento, então confirmar depois da escrita transforma uma query lenta numa tempestade de retentativas auto-amplificada.
  • As Melhores Fixtures de Teste Já Estavam em Produção: o monolito vinha armazenando toda notificação bruta de loja por anos. Todas as 720.183 transformaram um exercício de fixtures num exercício de replay.
  • Idempotência Pertence ao Banco de Dados: verificações de duplicata no nível da aplicação são necessárias e insuficientes. Sob entrega concorrente, a única coisa que se sustenta de forma confiável é uma constraint.

Portar e entregar

A linha adjacente

Correndo em paralelo a isso, em outro repositório, havia uma avaliação de se um provedor de dados esportivos poderia substituir outro. Os dois projetos se encontraram exatamente uma vez, no levantamento de acoplamentos acima: entidades chaveadas a um único provedor são justamente aquelas em que uma migração de provedor deixa de ser um exercício de integração e se torna um exercício de migração de dados.