DDD: Context Mapping - Mapeando as relações entre contextos (Parte 9)
Este é o nono artigo da série sobre Domain-Driven Design. Nos artigos anteriores, exploramos Bounded Contexts e como eles delimitam fronteiras claras entre diferentes áreas de negócio. Agora chegou o momento de entender como esses contextos se relacionam: Context Mapping.
Context Mapping é uma das práticas mais estratégicas do DDD, mas frequentemente negligenciada. Não é apenas sobre integração técnica - é sobre relacionamentos organizacionais e política de equipes.
O que é Context Mapping?
Context Mapping é a prática de mapear os relacionamentos entre diferentes Bounded Contexts. Eric Evans define:
"Um Context Map é uma representação visual dos Bounded Contexts e dos relacionamentos entre eles."
Context Mapping vai além de um diagrama. É uma ferramenta para:
- Identificar dependências entre contextos
- Visualizar fluxos de dados e comunicação
- Entender política organizacional e estrutura de equipes
- Planejar estratégias de integração
Os Padrões de Relacionamento
1. Customer-Supplier (Cliente-Fornecedor)
Relacionamento onde um contexto (upstream) fornece serviços para outro (downstream).
// Contexto Upstream: Catálogo de Produtos
class CatalogoService {
async obterProduto(id: string): Promise<ProdutoDTO> {
const produto = await this.produtoRepository.buscarPorId(id);
return {
id: produto.id,
nome: produto.nome,
preco: produto.preco.valor,
disponivel: produto.isDisponivel()
};
}
}
// Contexto Downstream: Carrinho de Compras
class CarrinhoService {
constructor(private catalogoService: CatalogoService) {}
async adicionarItem(carrinhoId: string, produtoId: string): Promise<void> {
const produto = await this.catalogoService.obterProduto(produtoId);
if (!produto.disponivel) {
throw new Error('Produto não disponível');
}
const carrinho = await this.carrinhoRepository.buscarPorId(carrinhoId);
carrinho.adicionarItem(produto);
await this.carrinhoRepository.salvar(carrinho);
}
}
2. Conformist (Conformista)
O contexto downstream aceita totalmente o modelo do upstream.
// Sistema Externo: Pagamento
interface PagamentoExternoAPI {
processarPagamento(dados: {
card_number: string;
amount_cents: number;
currency: string;
}): Promise<{ transaction_id: string; status: string }>;
}
// Contexto Conformista
class ProcessadorPagamento {
constructor(private pagamentoAPI: PagamentoExternoAPI) {}
async processarPagamento(
card_number: string,
amount_cents: number,
currency: string
): Promise<{ transaction_id: string; status: string }> {
return await this.pagamentoAPI.processarPagamento({
card_number,
amount_cents,
currency
});
}
}
3. Anticorruption Layer (Camada Anticorrupção)
O contexto downstream protege seu modelo através de uma camada de tradução.
// Sistema Legado
interface ERPLegado {
buscarCliente(codigo: string): {
cod_cli: string;
nm_cli: string;
dt_nasc: string; // DD/MM/AAAA
tp_cli: number; // 1=PF, 2=PJ
};
}
// Anticorruption Layer
class ClienteAnticorruptionLayer {
constructor(private erpLegado: ERPLegado) {}
async obterCliente(id: string): Promise<Cliente> {
const clienteERP = await this.erpLegado.buscarCliente(id);
return new Cliente(
clienteERP.cod_cli,
clienteERP.nm_cli,
this.parseData(clienteERP.dt_nasc),
clienteERP.tp_cli === 1 ? TipoCliente.FISICA : TipoCliente.JURIDICA
);
}
private parseData(dataString: string): Date {
const [dia, mes, ano] = dataString.split('/');
return new Date(parseInt(ano), parseInt(mes) - 1, parseInt(dia));
}
}
4. Shared Kernel (Núcleo Compartilhado)
Dois contextos compartilham um pequeno modelo comum.
// Núcleo compartilhado
interface ItemPedido {
readonly produtoId: string;
readonly quantidade: number;
readonly preco: Dinheiro;
}
// Contexto de Vendas
class PedidoVenda {
constructor(
public readonly id: string,
public readonly itens: ItemPedido[]
) {}
}
// Contexto de Logística
class PedidoExpedicao {
constructor(
public readonly id: string,
public readonly itens: ItemPedido[]
) {}
}
5. Open Host Service (Serviço de Host Aberto)
Um contexto oferece um protocolo público para integração.
interface NotificacaoAPI {
enviarEmail(request: {
destinatario: string;
assunto: string;
corpo: string;
}): Promise<{ id: string; status: string }>;
enviarSMS(request: {
telefone: string;
mensagem: string;
}): Promise<{ id: string; status: string }>;
}
class NotificacaoService implements NotificacaoAPI {
async enviarEmail(request: {
destinatario: string;
assunto: string;
corpo: string;
}): Promise<{ id: string; status: string }> {
const notificacao = Notificacao.criarEmail(
request.destinatario,
request.assunto,
request.corpo
);
await this.notificacaoRepository.salvar(notificacao);
return { id: notificacao.id, status: 'enviado' };
}
async enviarSMS(request: {
telefone: string;
mensagem: string;
}): Promise<{ id: string; status: string }> {
const notificacao = Notificacao.criarSMS(
request.telefone,
request.mensagem
);
await this.notificacaoRepository.salvar(notificacao);
return { id: notificacao.id, status: 'enviado' };
}
}
Exemplo Prático: E-commerce
Vamos mapear os contextos de um sistema de e-commerce:
class EcommerceContextMap {
definirContextos(): void {
// Catálogo: gerencia produtos e categorias
// Carrinho: gerencia itens do carrinho
// Pedidos: processa pedidos e pagamentos
// Estoque: controla disponibilidade
// Entrega: gerencia logística
}
definirRelacionamentos(): void {
// Catálogo -> Carrinho: Customer-Supplier
// Carrinho -> Pedidos: Customer-Supplier
// Pedidos -> Estoque: Customer-Supplier
// Pedidos -> Entrega: Customer-Supplier
// Estoque <-> Entrega: Shared Kernel (produto)
}
}
Ferramentas de Context Mapping
Visualização do Mapa
class ContextMapRenderer {
gerarDiagrama(contextos: BoundedContext[]): string {
return `
graph TD
Catalogo[Catálogo]
Carrinho[Carrinho]
Pedidos[Pedidos]
Estoque[Estoque]
Entrega[Entrega]
Catalogo -->|Customer-Supplier| Carrinho
Carrinho -->|Customer-Supplier| Pedidos
Pedidos -->|Customer-Supplier| Estoque
Pedidos -->|Customer-Supplier| Entrega
Estoque <-->|Shared Kernel| Entrega
`;
}
}
Validação do Context Map
class ContextMapValidator {
validar(contextMap: ContextMap): ValidacaoResult {
const problemas: string[] = [];
if (this.temCiclos(contextMap)) {
problemas.push('Context Map contém dependências circulares');
}
const sharedKernels = this.contarSharedKernels(contextMap);
if (sharedKernels > 2) {
problemas.push(`Muitos Shared Kernels (${sharedKernels}). Considere outras estratégias.`);
}
return { valido: problemas.length === 0, problemas };
}
private temCiclos(contextMap: ContextMap): boolean {
// Implementa detecção de ciclos
return false;
}
private contarSharedKernels(contextMap: ContextMap): number {
// Conta relacionamentos Shared Kernel
return 0;
}
}
Padrões Organizacionais
Context Mapping reflete a estrutura organizacional (Lei de Conway):
// Time por contexto
class TimeContexto {
constructor(
public readonly nome: string,
public readonly contexto: BoundedContext,
public readonly membros: string[]
) {}
}
// Coordenação entre times
class CoordenacaoTimes {
coordenarIntegracao(
timeUpstream: TimeContexto,
timeDownstream: TimeContexto,
tipoRelacionamento: string
): void {
switch (tipoRelacionamento) {
case 'Customer-Supplier':
this.estabelecerContratoAPI(timeUpstream, timeDownstream);
break;
case 'Shared Kernel':
this.definirGovernancaCompartilhada(timeUpstream, timeDownstream);
break;
}
}
private estabelecerContratoAPI(upstream: TimeContexto, downstream: TimeContexto): void {
// Define SLA, contratos de API, etc.
}
private definirGovernancaCompartilhada(time1: TimeContexto, time2: TimeContexto): void {
// Define processo de mudanças no núcleo compartilhado
}
}
Conclusão
Context Mapping é uma ferramenta estratégica fundamental no DDD que vai além de diagramas técnicos. É sobre entender relacionamentos, gerenciar dependências e alinhar tecnologia com organização.
Pontos-chave:
- Context Mapping reflete a realidade organizacional
- Cada padrão de relacionamento tem implicações diferentes
- Shared Kernel deve ser usado com parcimônia
- Anticorruption Layer protege o modelo de domínio
- Context Map evolui com o tempo
- Validação contínua identifica problemas arquiteturais
Um Context Map bem desenhado resulta em integração mais limpa, evolução independente e redução de conflitos entre equipes.
No próximo artigo da série, exploraremos Anti-Corruption Layer em detalhes.
Referências
- Domain-Driven Design: Tackling Complexity in the Heart of Software - Eric Evans
- Context Mapping - Martin Fowler
- Team Topologies - Matthew Skelton & Manuel Pais
- Patterns, Principles, and Practices of Domain-Driven Design - Scott Millett
- Context Mapping - DDD Community
- Implementing Domain-Driven Design - Vaughn Vernon