Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

04 - Anti-Corruption Layer (ACL)

#acl#adaptador#anti-corruption#legado#tradução
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Anti-Corruption Layer (ACL)

A Anti-Corruption Layer é uma camada de tradução colocada entre o seu modelo de domínio e o modelo de um sistema externo, com a função explícita de impedir que conceitos estranhos contaminem o domínio próprio. Ela funciona como fronteira linguística: o lado interno fala a linguagem ubíqua do seu contexto, o lado externo fala o protocolo e o vocabulário do sistema integrado, e a camada faz a tradução bidirecional em um único lugar. Em produção, a ACL é a estratégia padrão para integrar sistemas legados, produtos de terceiros com modelos estranhos e qualquer upstream cujo vocabulário não se deseja adotar. O benefício é a soberania do modelo: o domínio evolui conforme o negócio exige, sem pedir licença às distorções alheias. O custo é permanente: toda mudança no sistema externo exige atualização da tradução, e a camada precisa ser mantida com a mesma seriedade do domínio que protege. Entender quando o preço vale a proteção é o que separa uma ACL saudável de uma burocracia de mapeamento.

Conceito e propósito

O nome descreve a função com precisão. Corrupção, aqui, é a invasão de conceitos externos ao modelo do contexto, e ela raramente chega de forma óbvia. Ela chega em formato de campo chamado cust_type no meio do payload, de estado cujo significado real é descoberto por tentativa e erro, de entidade que amarra três conceitos diferentes no mesmo registro. Sem proteção, esses conceitos se infiltram: uma classe do domínio passa a ter um atributo legado, depois uma regra que consulta esse atributo, depois uma migração que o propaga para novas tabelas. Dois anos depois, ninguém sabe distinguir o que é domínio e o que é fóssil de integração.

A ACL inverte a direção da dependência conceitual. O sistema externo não define o que o seu domínio significa; ele apenas fornece dados e operações, e a tradução decide como cada um deles é representado internamente. O domínio nunca importa tipos do cliente externo, nunca grava estruturas cruas do payload e nunca consulta estados estrangeiros em suas regras. Tudo isso é responsabilidade exclusiva da camada.

Vale notar que o adversário da camada não é tecnologia, e sim vocabulário. Duas equipes podem descrever o mesmo fato comercial com conjuntos de conceitos diferentes, e nenhuma das descrições está errada; elas são modelos diferentes. A ACL existe porque os dois modelos precisam coexistir sem que o maior e mais antigo enterre o menor e mais novo. Nesse sentido, a camada é uma decisão de linguística aplicada antes de ser uma decisão de código.

O problema que resolve

O cenário clássico é a integração com o sistema de retaguarda que administra o negócio há quinze anos. O sistema legado modela cliente como cadastro único, com tipo de conta em código numérico, status com valores que misturam situação fiscal e preferência de contato, e identificadores que reaproveita após exclusão. Seu contexto novo de fidelidade precisa de um conceito de Membro com regras próprias, e a pergunta é como consumir o legado sem virar legado.

Sem ACL, as três evoluções típicas de corrupção se desenrolam em sequência previsível. Primeiro, o modelo local ganha o campo tipo_conta como int, porque era assim que vinha do legado. Depois, regras de negócio passam a comparar esse número diretamente, espalhando o significado mágico pelo código. Por fim, a equipe descobre que o legado reaproveita identificadores, e correções pontuais começam a surgir em cada canto que confiou na unicidade. Com ACL, cada uma dessas peculiaridades é absorvida em um lugar: o tradutor converte tipo_conta em um conceito local com semântica clara, valida a suposição de unicidade na fronteira e documenta a regra do legado em um único arquivo de tradução.

Anatomia: fachada, adaptador e tradutor

A composição clássica da ACL tem três papéis, cada um com responsabilidade distinta. A fachada apresenta uma interface simples e conceitual para o serviço externo, no vocabulário do contexto consumidor. O adaptador implementa essa fachada usando a tecnologia real do upstream, ou seja, sabe fazer chamadas HTTP, consultas ou invocações de fila. O tradutor converte os dados entre os dois mundos, e é o único lugar onde os dois vocabulários coexistem.

# ACL do contexto de fidelidade protegendo-se do sistema legado de cadastro

class FachadaCadastro:                       # interface no vocabulário local
    def obter_membro(self, id_membro: str) -> Membro: ...

class AdaptadorCadastroLegado(FachadaCadastro):
    def __init__(self, cliente_legado, tradutor: TradutorMembro):
        self.cliente = cliente_legado
        self.tradutor = tradutor

    def obter_membro(self, id_membro: str) -> Membro:
        registro = self.cliente.buscar_por_id(int(id_membro))
        if registro is None:
            raise MembroInexistente(id_membro)      # erro local, não erro do legado
        return self.tradutor.para_membro(registro)  # conversão única e explícita

class TradutorMembro:
    MAPA_TIPO_CONTA = {1: Categoria.PF_ATIVO, 2: Categoria.PJ_ATIVO, 3: Categoria.INATIVO}

    def para_membro(self, registro_legado) -> Membro:
        categoria = self.MAPA_TIPO_CONTA.get(registro_legado.cust_type)
        if categoria is None:
            # código desconhecido do legado não entra no domínio como número mágico
            raise TraducaoIndefinida("cust_type", registro_legado.cust_type)
        return Membro(
            id=IdentidadeMembro(registro_legado.codigo),
            nome=registro_legado.full_name.strip() or None,
            categoria=categoria,
        )

O detalhe que separa uma ACL real de um simples wrapper é o tratamento de tradução indefinida. Quando o legado traz um código que o mapa não conhece, a camada não repassa o número cru para dentro do domínio, nem escolhe um default silencioso; ela sinaliza a falha de tradução, com log e métrica, e a equipe decide o significado do conceito novo. Esse comportamento transforma cada evolução do upstream em evento visível, e não em bug descoberto por conciliação mensal.

Tradução explícita e semântica preservada

A tradução de uma ACL não é renomeação de campos, é reconstrução de significado. Diferenças semânticas comuns exigem decisão documentada: identificadores reaproveitados pelo legado precisam de tratamento que garanta unicidade percebida pelo domínio; estados que misturam conceitos no legado precisam ser decompostos em conceitos locais separados; valores de dinheiro em precisão diferente precisam de conversão em um único ponto.

Boas práticas de tradução, com base em integrações reais:

  • Toda conversão passa por função de tradutor testável, nunca por espalhamento de mapeamento pelo código do domínio.
  • Tradução parcial é proibida: ou o registro entra completo e coerente no domínio, ou não entra, porque dado meio traduzido é dado corrupto com aparência saudável.
  • Valores desconhecidos geram evento de tradução indefinida, com alerta, em vez de default silencioso que finge coerência.
  • O mapa de tradução fica versionado junto do código, com comentário explicando a regra do legado, que é o conhecimento mais frágil da integração.
  • Testes da ACL usam payloads reais capturados em produção como casos de regressão, cobrindo as peculiaridades que só o legado produz.

Integração com sistemas legados

A ACL é a estratégia natural para legado, e a razão é a assimetria de poder. O sistema antigo não vai se adaptar, não tem roadmap para atender você e não roda seus testes. A proteção precisa, portanto, viver toda no lado consumidor, e é exatamente o desenho da ACL.

Em projetos de modernização, a ACL combina com o padrão strangler fig. O novo contexto se conecta ao legado apenas pela camada anticorrupção, que inicialmente traduz chamadas diretas; com o tempo, as capacidades do legado são reimplementadas atrás da fachada, e os consumidores não mudam nada, porque a interface local permanece. A fachada que nasceu como tradução termina como substituição, e esse caminho gradual é o que permite modernizar sem grande projeto de corte.

O inventário de peculiaridades é o ativo mais valioso dessa integração. Cada surpresa do legado, reaproveitamento de id, código de tipo sem documentação, registro com vírgula no nome quebrando o parser, deve virar teste na suíte da ACL. A suíte cresce como uma coleção de fósseis documentados, e a memória da integração passa a viver no código, e não na cabeça de um especialista que pode sair da empresa.

O canal assíncrono recebe a mesma proteção, com ajuste de forma. Eventos publicados por um sistema externo chegam com schema próprio, e a ACL do consumidor traduz cada evento para o formato de domínio antes de qualquer processamento. Os mesmos princípios valem: nenhum evento cru avança, valores desconhecidos geram alerta, e a suíte de regressão guarda exemplos reais. Esse desenho evita o erro comum de espalhar conversões de payload entre os handlers, que é o equivalente assíncrono do mapeamento espalhado pelo código.

Custo de manutenção e trade-offs

Aspecto Efeito na produção
Custo inicial Alto: desenhar fachada, adaptador, tradutor e suíte de tradução
Custo recorrente Cada mudança relevante do upstream exige atualização e teste da camada
Custo de aprendizado Time precisa dominar dois modelos, o local e o externo, ao menos na fronteira
Latência e complexidade Adiciona um salto de código por chamada, irrelevante na maioria dos casos
Isolamento de falha Erros do upstream aparecem como falha de tradução, e não como bug de domínio
Velocidade de evolução do domínio Alta, porque o modelo local muda sem coordenação com o upstream
Risco de drift Baixo, desde que a tradução seja o único ponto de contato com o externo

O trade-off central é o mesmo de qualquer seguro: você paga prêmio contínuo para evitar perda potencial grande. A perda evitada é a corrupção do modelo, cujo custo real aparece em integrações que ficaram anos sem proteção e hoje precisam de meses de trabalho para separar domínio de fóssil. A regra de decisão pragmática: quanto mais distante e instável o upstream em relação ao seu domínio, mais a ACL se paga; quanto mais estável e próximo o modelo upstream, mais a tradução parece burocracia, e o Conformist ou o Shared Kernel tendem a ser a resposta melhor.

Critério de escolha Favorece ACL Favorece Conformist
Estabilidade do modelo upstream Instável ou em evolução imprevisível Estável há anos
Qualidade semântica do modelo externo Conceitos confusos ou misturados Conceitos claros e adotáveis
Importância do domínio integrado Diferenciador de negócio Capacidade de suporte
Horizonte da integração Longo prazo, troca de provedor prevista Curto prazo ou fornecedor já consolidado
Poder de negociação com o upstream Nenhum, sistema alheio Alto, relação interna saudável

Aplicação prática: passo a passo

A construção de uma ACL que sobrevive ao uso segue uma sequência estabelecida:

  1. Delimite a fronteira: escolha o conjunto mínimo de operações e dados do upstream que o contexto realmente usa, e resista a traduzir o sistema inteiro.
  2. Escreva a fachada no vocabulário local, antes de olhar o formato externo, para que a interface seja guiada pelo domínio e não pelo payload.
  3. Implemente o adaptador com a tecnologia real do upstream, isolando SDK, autenticação e protocolo dentro dele.
  4. Escreva o tradutor com o inventário de peculiaridades coletado, incluindo o tratamento explícito de valores desconhecidos.
  5. Monte a suíte de tradução com payloads reais capturados, incluindo os casos grotescos que só aparecem em produção.
  6. Proíba, por revisão de código, qualquer import do cliente externo fora da camada, com verificação automatizada quando possível.
  7. Instrumente traduções indefinidas como métrica e alerta, porque elas são o radar de evolução do upstream.

A ordem do passo 2 é a mais desrespeitada e a mais importante. Escrever a fachada olhando primeiro o payload do upstream produz uma camada que espelha o estrangeiro com nomes trocados, o pior dos dois mundos: o custo da tradução sem o benefício do modelo próprio. A fachada guiada pelo domínio, em contrapartida, funciona como especificação do que o contexto precisa, e o adaptador é preenchido depois para atender essa especificação. Essa inversão de direção é o que impede a ACL de degenerar em wrapper de conveniência.

Erros comuns e boas práticas

Sintoma em produção Causa raiz Mitigação
Conceito do legado aparece em regra de domínio Tradução incompleta, dado cru vazando para dentro Tradução total na fronteira, sem exceção
Bug de integração corrigido em vários pontos Mapeamento espalhado pelo código em vez de concentrado Consolidar no tradutor e auditar imports
Upstream muda e ninguém percebe até a reclamação Sem tratamento de tradução indefinida Alerta de falha de tradução com triagem
ACL vira monolito de utilidades Fronteira mal delimitada, traduzindo tudo Reduzir ao conjunto mínimo de operações usadas
Dados divergentes entre domínio e upstream Tradução parcial aplicada em caminhos diferentes Tradutor único testado com payloads reais
Conhecimento do legado preso em uma pessoa Peculiaridades sem teste nem comentário Suíte de regressão como memória da integração

A boa prática estruturante é tratar a ACL como módulo de primeira classe, com dono, testes e pipeline, e não como pasta de utilidades. A camada tem a responsabilidade mais delicada da integração, que é absorver a instabilidade alheia, e merece o mesmo rigor de engenharia do domínio que protege. A segunda prática é a revisão periódica do mapa de tradução contra o sistema externo; provedores depreciam campos silenciosamente, e uma revisão trimestral comparando o payload real com o mapa captura essas mudanças antes do incidente. A terceira é resistir à tentação de pular a camada em casos urgentes; o import direto de emergência, justificado pelo prazo, é o primeiro furo da fronteira, e furos de fronteira não se fecham sozinhos.