DDD: Context Mapping - Mapeando as relações entre contextos (Parte 9)

DDD6 min de leitura

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:

  1. Context Mapping reflete a realidade organizacional
  2. Cada padrão de relacionamento tem implicações diferentes
  3. Shared Kernel deve ser usado com parcimônia
  4. Anticorruption Layer protege o modelo de domínio
  5. Context Map evolui com o tempo
  6. 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