O parceiro matou a API legada e te deu 30 dias. E agora?
Imagina que é uma manhã normal. Café na mão, você abre o e-mail e tem uma mensagem do time de desenvolvedores de um grande marketplace de delivery — aquele por onde passa boa parte do faturamento dos seus clientes. O assunto já dá um frio na barriga:
“A partir de 30 de junho, a API legada será descontinuada. A migração para a nova API é obrigatória em todos os módulos — autenticação, produtos, pedidos. Integradoras que não concluírem a migração terão a operação dos lojistas interrompida.”
Traduzindo do corporativês: migre em 30 dias ou seus clientes param de vender. E não é um módulo, é tudo — login, catálogo, recebimento de pedido. O coração da integração.
Se você nunca passou por isso, parabéns, aproveite. Quem vive de integrar com terceiros sabe que esse e-mail chega. Sempre chega. A pergunta nunca é “se”, é “quando” — e o quanto a sua arquitetura vai te ajudar ou te afundar quando ele chegar.
Esse artigo é sobre o segundo cenário virar o primeiro.
Por que isso dói tanto (e por que a culpa às vezes é nossa)
O motivo de uma deprecation dessas virar pesadelo quase nunca é a API nova ser difícil. É que a API antiga vazou pra dentro do seu sistema.
Sabe aquele código que foi escrito com pressa lá no começo? O Http::post('https://parceiro.com/v1/orders', $dados) espalhado em controller, em job, em command, em service. O json_decode do response do parceiro sendo lido direto na regra de negócio, com $response['order']['items'][0]['unit_price'] enfiado no meio do cálculo do pedido.
Quando o formato do parceiro está espalhado em 40 arquivos, trocar de API não é uma migração. É uma caçada. Você vai passar 30 dias com grep procurando todo lugar que toca a API antiga, rezando pra não esquecer nenhum — e vai esquecer.
A boa notícia: dá pra evitar isso. E mesmo que você já esteja no buraco, dá pra sair dele de um jeito controlado.
A defesa: Anti-Corruption Layer
O conceito vem do DDD e tem um nome dramático: Anti-Corruption Layer (ACL). A ideia, porém, é simples e quase óbvia quando você vê: o seu domínio não fala o idioma do parceiro. Ele fala o seu idioma. No meio, tem uma camada de tradução.
Na prática, isso começa com uma interface que descreve o que o seu sistema precisa — não o que o parceiro oferece:
interface CanalDeDelivery
{
public function autenticar(Loja $loja): TokenDeAcesso;
/** @return Pedido[] */
public function buscarPedidosPendentes(Loja $loja): array;
public function confirmarPedido(Pedido $pedido): void;
}
Repara que aqui não tem array, não tem JSON, não tem header HTTP, não tem nome de campo do parceiro. Tem Pedido, Loja, TokenDeAcesso — objetos do seu domínio. O resto do sistema só conhece essa interface. Ele não sabe (nem quer saber) se por baixo é a API v1, a v2 ou um pombo-correio.
O Adapter: onde a tradução acontece
Cada versão da API do parceiro vira um Adapter que implementa essa interface. A API legada:
final class CanalDeliveryServicesApi implements CanalDeDelivery
{
public function __construct(private readonly ClienteHttp $http) {}
public function buscarPedidosPendentes(Loja $loja): array
{
$resposta = $this->http->get("/v1/orders", [
'merchant_id' => $loja->idExterno(),
'status' => 'PENDING',
]);
// A bagunça do formato do parceiro MORRE aqui dentro.
return array_map(
fn (array $cru) => Pedido::deDeliveryLegado($cru),
$resposta['orders'] ?? []
);
}
// autenticar(), confirmarPedido()...
}
E a API nova, que mudou rota, nomes de campo e estrutura:
final class CanalDeliveryMerchantApi implements CanalDeDelivery
{
public function __construct(private readonly ClienteHttp $http) {}
public function buscarPedidosPendentes(Loja $loja): array
{
// Rota nova, paginação nova, nomes de campo novos.
$resposta = $this->http->get("/order/v2.0/merchants/{$loja->idExterno()}/orders");
return array_map(
fn (array $cru) => Pedido::deDeliveryMerchant($cru),
$resposta['data'] ?? []
);
}
// autenticar(), confirmarPedido()...
}
Olha o que aconteceu: a diferença entre as duas APIs ficou confinada em dois arquivos. O unit_price que virou price.amount, a rota que mudou, o status que antes era PENDING e agora é um enum diferente — tudo isso é problema do adapter, e só dele. O cálculo do pedido, o job que processa, o controller… nada disso muda uma linha. Eles continuam falando com CanalDeDelivery.
Esse é o pulo do gato: a deprecation deixou de ser uma caçada de 40 arquivos e virou a escrita de uma classe nova.
A virada de chave: feature flag por loja
Agora a parte que separa quem migra com sono tranquilo de quem migra no susto. Você não vira a chave de todos os clientes de uma vez na meia-noite do dia 30. Isso é pedir pra ter um incidente em massa sem ter como diagnosticar.
Você usa uma feature flag — de preferência por loja — pra escolher qual adapter entregar:
final class CanalDeliveryFactory
{
public function __construct(
private readonly FeatureFlags $flags,
private readonly CanalDeliveryServicesApi $legado,
private readonly CanalDeliveryMerchantApi $novo,
) {}
public function para(Loja $loja): CanalDeDelivery
{
return $this->flags->habilitada('merchant_api', $loja)
? $this->novo
: $this->legado;
}
}
Com isso você migra uma loja, observa por um dia, migra dez, observa, migra cem. Se algo der errado, o rollback é mudar uma flag — não é um deploy de madrugada com o coração na mão. Quando chegar o dia 30, a esmagadora maioria já vai estar na API nova, testada em produção, e você dorme.
Shadow traffic: testando a API nova com dados reais antes de confiar nela
Tem um truque a mais, pra quem quer ir além. Antes de confiar na API nova pra valer, você pode rodar as duas em paralelo: a antiga responde de verdade, e a nova roda “na sombra”, só pra você comparar os resultados e logar as divergências.
final class CanalDeliveryComSombra implements CanalDeDelivery
{
public function __construct(
private readonly CanalDeDelivery $principal, // o legado, que vale
private readonly CanalDeDelivery $sombra, // o novo, em teste
private readonly Logger $log,
) {}
public function buscarPedidosPendentes(Loja $loja): array
{
$oficial = $this->principal->buscarPedidosPendentes($loja);
try {
$teste = $this->sombra->buscarPedidosPendentes($loja);
if (count($teste) !== count($oficial)) {
$this->log->warning('Divergência na Merchant API', [
'loja' => $loja->idExterno(),
'oficial' => count($oficial),
'sombra' => count($teste),
]);
}
} catch (\Throwable $e) {
// A sombra NUNCA pode derrubar o fluxo real.
$this->log->error('Sombra falhou', ['erro' => $e->getMessage()]);
}
return $oficial; // o cliente sempre recebe o resultado confiável
}
}
Em uma ou duas semanas de sombra você descobre, com tráfego real, todos os cantos onde a API nova se comporta diferente — sem nenhum cliente ser afetado. Quando você finalmente vira a flag, já não tem surpresa.
Não esqueça da idempotência
Um detalhe que morde muita gente em migração de integração de pedido e pagamento: confirmar o mesmo pedido duas vezes durante a transição (porque você processou no fluxo antigo e reprocessou no novo) pode gerar duplicidade. Sempre que a operação não for naturalmente idempotente, mande uma chave de idempotência — quase toda API séria de pagamento/pedido suporta isso:
$this->http->post("/order/v2.0/orders/{$pedido->id()}/confirm", [
'headers' => ['Idempotency-Key' => $pedido->chaveIdempotencia()],
]);
Reenviar com a mesma chave é seguro: o parceiro reconhece e não processa duas vezes.
O impacto técnico, sem hype
Nada disso é tecnologia nova. ACL, Adapter, feature flag e shadow traffic são padrões com anos de estrada. O ponto do artigo não é a novidade — é quando você paga esse custo.
Se você isola a integração atrás de uma interface desde o dia zero, a deprecation do parceiro vira um ticket de uma semana: escrever um adapter, rodar na sombra, virar flag loja a loja. Se você não isola, o mesmo e-mail vira 30 dias de caçada, deploy de madrugada e dedo cruzado.
A diferença entre os dois mundos é literalmente uma interface e um pouco de disciplina lá no começo, quando ninguém estava te cobrando isso ainda. É o tipo de decisão que não aparece em nenhuma sprint, mas que define se você vai surfar ou se afogar quando o parceiro mudar as regras — e ele vai mudar.
O e-mail vai chegar. Pode ser delivery, pode ser meio de pagamento, pode ser o ERP, pode ser o gateway de nota fiscal. Algum parceiro vai descontinuar uma API que está no caminho crítico do seu faturamento e vai te dar um prazo apertado.
A pergunta que importa não é “como eu migro rápido”. É “o formato desse parceiro está vazado pra dentro do meu domínio ou trancado num adapter?”. Se estiver trancado, você respira fundo, escreve uma classe, roda na sombra e vira uma flag. Se estiver vazado… bom, melhor começar a trancar hoje, com calma, antes do próximo e-mail.
Sua regra de negócio merece falar o seu idioma. Deixa o idioma do parceiro do lado de fora do muro.


