Anti-corruption layer: protegendo domínios de sistemas legados
1. Introdução ao Conceito de Anti-corruption Layer (ACL)
1.1. Definição e origem no Domain-Driven Design (Eric Evans)
A Anti-corruption Layer (ACL) é um padrão arquitetural introduzido por Eric Evans em seu livro Domain-Driven Design: Tackling Complexity in the Heart of Software (2003). O conceito nasce da necessidade de proteger um modelo de domínio rico e bem definido contra a contaminação de conceitos, regras e estruturas de sistemas externos — especialmente sistemas legados que possuem modelos pobres, inconsistentes ou com semântica distorcida.
1.2. Problema central: corrupção do modelo de domínio por sistemas externos legados
Quando um sistema moderno precisa se integrar a um sistema legado, há uma tendência natural de "importar" os conceitos do legado para o novo domínio. Isso gera corrupção: entidades do domínio limpo passam a carregar atributos desnecessários, regras de negócio do legado migram para o novo sistema, e o modelo perde sua pureza e coesão. A ACL atua como uma barreira que traduz, filtra e adapta as comunicações entre os dois mundos.
1.3. Diferença entre ACL e outras estratégias de integração
Diferente do Shared Kernel, onde dois bounded contexts compartilham um subconjunto do modelo, a ACL mantém total separação. Já o Open Host Service expõe um serviço padronizado para consumo externo; a ACL, por outro lado, é uma camada que consome serviços externos e os adapta ao modelo interno. A ACL é essencialmente uma ponte defensiva, não uma interface pública.
2. Quando e Por Que Utilizar uma ACL
2.1. Cenários típicos
- Sistemas legados monolíticos: bancos de dados com esquemas normalizados de forma inadequada, stored procedures com lógica embutida, APIs REST inconsistentes.
- APIs de terceiros: provedores que mudam contratos sem aviso, modelos de dados genéricos demais.
- Bancos de dados não normalizados: tabelas que misturam conceitos de diferentes domínios, campos com múltiplos significados.
2.2. Sinais de alerta
- Código do domínio limpo contém
if (legacyField == "Y")ou referências diretas a nomes de colunas do banco legado. - Alterações no sistema legado quebram funcionalidades no novo sistema sem relação aparente.
- Testes de unidade precisam simular o comportamento do legado para validar regras de negócio.
2.3. Trade-offs
Implementar uma ACL exige investimento inicial em mapeamento, adaptadores e testes. O ganho é enorme em manutenibilidade: o domínio limpo evolui independentemente do legado, e a substituição gradual do sistema antigo se torna viável sem refatoração catastrófica.
3. Estrutura Típica de uma Anti-corruption Layer
3.1. Componentes principais
- Adaptadores: traduzem chamadas entre interfaces (ex: REST do legado → interface do repositório do domínio).
- Tradutores (Translators): convertem objetos de um modelo para outro (ex:
LegacyCustomerDTO→Customer). - Fachadas (Facades): simplificam interações complexas com o legado, expondo apenas o necessário.
- Serviços de integração: orquestram chamadas, tratam erros, implementam retry e circuit breaker.
3.2. Fluxo de dados
Sistema Legado → ACL (Adaptador → Tradutor → Fachada) → Domínio Limpo
Domínio Limpo → ACL (Fachada → Tradutor → Adaptador) → Sistema Legado
3.3. Separação entre modelos
O modelo legado (DTOs, entidades anêmicas) jamais deve vazar para o domínio limpo. A ACL é responsável por criar uma representação limpa e semanticamente adequada.
4. Estratégias de Implementação da ACL
4.1. Tradução de objetos
Mapeamento explícito entre DTOs legados e entidades de domínio, preferencialmente com testes que validem cada campo.
4.2. Fachada de serviço
Encapsula chamadas complexas (múltiplos endpoints, autenticação, paginação) em uma interface limpa.
4.3. Adaptador de repositório
Isola consultas e persistência, aplicando filtros e normalizações antes de expor dados ao domínio.
5. Exemplos Práticos de Código (em texto)
5.1. Exemplo 1: Tradução de um modelo de cliente legado para o domínio limpo
// Modelo legado (vindo de API externa)
class LegacyCustomerDTO {
String id;
String full_name; // "SILVA, João"
String status_code; // "A" = ativo, "I" = inativo
String last_purchase_date; // "20230115"
}
// Modelo de domínio limpo
class Customer {
CustomerId id;
FullName name; // objeto valor com firstName, lastName
boolean isActive;
LocalDate lastPurchaseDate;
}
// Tradutor
class CustomerTranslator {
Customer translate(LegacyCustomerDTO dto) {
String[] parts = dto.full_name.split(", ");
FullName name = new FullName(parts[1], parts[0]); // João Silva
return new Customer(
new CustomerId(dto.id),
name,
dto.status_code.equals("A"),
LocalDate.parse(dto.last_purchase_date, DateTimeFormatter.BASIC_ISO_DATE)
);
}
}
5.2. Exemplo 2: Fachada que converte chamadas REST inconsistentes em eventos de domínio
// Fachada que consome API legada e produz eventos limpos
class LegacyOrderFacade {
private final LegacyApiClient client;
private final OrderTranslator translator;
private final EventPublisher publisher;
void fetchAndPublishOrders() {
List<LegacyOrderDTO> rawOrders = client.getOrders("pending");
for (LegacyOrderDTO raw : rawOrders) {
OrderDomainEvent event = translator.toDomainEvent(raw);
publisher.publish(event);
}
}
}
// Evento de domínio limpo
class OrderPlacedEvent {
OrderId id;
CustomerId customerId;
Money total;
List<OrderItem> items;
}
5.3. Exemplo 3: Adaptador de repositório que filtra e normaliza dados de um banco legado
// Adaptador de repositório
class LegacyCustomerRepositoryAdapter implements CustomerRepository {
private final LegacyDatabase legacyDb;
private final CustomerTranslator translator;
@Override
public Customer findById(CustomerId id) {
LegacyCustomerRecord record = legacyDb.query(
"SELECT * FROM clientes WHERE codigo = ?", id.value()
);
if (record == null) return null;
// Filtra registros com status deletado
if ("D".equals(record.status)) return null;
return translator.translate(record);
}
@Override
public void save(Customer customer) {
// Converte para modelo legado e persiste
LegacyCustomerRecord record = translator.toLegacy(customer);
legacyDb.update("UPDATE clientes SET ... WHERE codigo = ?", record);
}
}
6. Testabilidade e Evolução da ACL
6.1. Como testar a camada de tradução isoladamente
Testes de unidade para tradutores são diretos: crie DTOs legados com valores conhecidos e verifique se as entidades de domínio são produzidas corretamente. Testes de integração validam a fachada com mocks do sistema legado.
6.2. Estratégias de versionamento
A ACL deve versionar seus contratos internos. Quando o sistema legado muda, apenas a ACL é alterada — o domínio limpo permanece intacto. Use testes de regressão para garantir que a tradução continua correta.
6.3. Refatoração gradual
A ACL permite substituir o legado por partes: um novo microsserviço pode ser introduzido atrás da mesma interface da ACL, e o domínio limpo não percebe a mudança.
7. Relação com Outros Padrões de Integração
7.1. ACL vs. Open Host Service
Enquanto o Open Host Service expõe um serviço padronizado para ser consumido, a ACL consome serviços externos. Um sistema pode ter ambos: um Open Host Service para seus clientes e uma ACL para consumir o legado internamente.
7.2. ACL e Context Mapping
No mapeamento de contextos (Context Mapping), a ACL é representada como uma camada entre dois bounded contexts, geralmente entre um contexto core (limpo) e um contexto legado (suporte ou genérico).
7.3. ACL combinada com Event Storming
Durante sessões de Event Storming, eventos de fronteira entre sistemas podem ser identificados. Esses eventos são candidatos naturais a serem tratados pela ACL, que pode traduzi-los para eventos de domínio internos.
8. Considerações Finais e Boas Práticas
8.1. Armadilhas comuns
- Superengenharia: criar uma ACL complexa para integrações simples que poderiam ser resolvidas com mapeamento direto.
- Acoplamento excessivo à tradução: a ACL pode crescer e se tornar um monolito próprio. Mantenha-a enxuta.
- Ignorar tratamento de erros: falhas no legado devem ser traduzidas para exceções do domínio, não propagadas como estão.
8.2. Recomendações
- Mantenha a ACL focada apenas no necessário para proteger o domínio.
- Documente cada mapeamento e decisão de tradução.
- Estabeleça testes de contrato que garantam que a ACL ainda funciona quando o legado muda.
8.3. Relação com outros artigos da série
Este artigo se conecta diretamente a outros padrões de integração abordados na série: Shared Kernel (compartilhamento controlado), Context Mapping (visão macro das fronteiras) e Modularity Metrics (como medir o isolamento alcançado pela ACL).
Referências
- Anti-Corruption Layer (Martin Fowler) — Artigo seminal de Martin Fowler explicando o padrão com exemplos práticos e contexto histórico.
- Domain-Driven Design: Tackling Complexity in the Heart of Software (Eric Evans) — Livro original que introduziu o conceito de Anti-corruption Layer, disponível no site oficial do autor.
- Anti-Corruption Layer Pattern (Microsoft Azure Architecture Center) — Documentação oficial da Microsoft com diagramas, cenários de uso e considerações de implementação.
- Implementing Anti-Corruption Layer in Microservices (InfoQ) — Artigo técnico detalhado sobre como implementar ACL em arquiteturas de microsserviços, com exemplos em Java e C#.
- Anti-Corruption Layer: Protecting Your Domain (Herberto Graça) — Blog post com explicação clara do padrão, incluindo exemplos de código e relação com outros padrões de DDD.
- Context Mapping and Anti-Corruption Layer (DDD Community) — Recurso da comunidade DDD com definição formal e exemplos de mapeamento de contextos.
- Refactoring Legacy Code with Anti-Corruption Layer (ThoughtWorks) — Artigo prático da ThoughtWorks sobre como usar ACL para refatorar sistemas legados gradualmente.