DDD Aplicado a IA: Bounded Contexts como Fronteiras de Agentes

Domain-Driven Design existe há 20 anos e resolve um problema específico: como organizar software complexo em módulos que fazem sentido para o negócio. Bounded Contexts são a ferramenta principal: cada contexto tem seu próprio modelo, sua própria linguagem, e suas próprias regras. O que ninguém esperava é que esse mesmo conceito se aplica perfeitamente a sistemas multi-agente. Cada bounded context vira a fronteira de um agente. O agente de "auth" não precisa saber o modelo de "payment". O agente de "catalog" não precisa saber as regras de "delivery". A separação que DDD propõe para código humano funciona igualmente bem para código gerado por IA.

Guia de tópicos:

  • Bounded Context: O Conceito em 30 Segundos
  • Por Que Agentes Precisam de Fronteiras
  • Mapeando Contexts para Agentes
  • Linguagem Ubíqua por Agente
  • Context Maps: Como Agentes se Relacionam
  • Anti-Corruption Layer entre Agentes
  • Detecção Automática de Contexts
  • Considerações Finais
  • Links Indicativos

Bounded Context: O Conceito em 30 Segundos

Um Bounded Context é uma fronteira explícita dentro da qual um modelo de domínio é consistente. Dentro do contexto de "Orders", um "Product" é um item com quantidade e preço unitário. Dentro do contexto de "Catalog", um "Product" é uma entidade com descrição, fotos, categorias e estoque. São o mesmo conceito do mundo real mas com modelos diferentes porque servem propósitos diferentes.

A regra é: dentro de um bounded context, todo mundo fala a mesma língua e usa o mesmo modelo. Entre bounded contexts, a comunicação acontece por contratos explícitos (APIs, eventos, interfaces). Um contexto nunca acessa diretamente o modelo interno de outro.

Isso resolve o problema do "modelo universal" que tenta representar tudo para todos e acaba não servindo bem para ninguém. Em vez de um Product com 50 campos (metade irrelevante para cada caso de uso), você tem modelos enxutos e específicos para cada contexto.

Por Que Agentes Precisam de Fronteiras

Quando um agente IA tem acesso a todo o código do projeto, ele tende a criar acoplamento. Pede para implementar "checkout" e ele importa diretamente o model de User do módulo de auth, o model de Product do módulo de catalog, e o service de Notification do módulo de messaging. Funciona, mas cria dependências que tornam qualquer mudança futura arriscada.

O agente faz isso porque não tem noção de fronteiras. Para ele, todo arquivo é igualmente acessível. Não existe o conceito de "isso é interno ao módulo X, você não deveria acessar diretamente". Ele otimiza para resolver o problema imediato, não para manutenibilidade de longo prazo.

Quando você dá ao agente apenas os arquivos do seu bounded context, ele é forçado a trabalhar dentro das fronteiras. Se o agente de "orders" precisa de informação do usuário, ele não pode importar o model de User diretamente (porque não tem acesso). Ele precisa usar a interface pública que o contexto de "auth" expõe. Isso é exatamente o que DDD prescreve, mas enforçado pela limitação de contexto do agente em vez de disciplina do desenvolvedor.

Mapeando Contexts para Agentes

O mapeamento é direto: cada bounded context do seu projeto vira um agente com escopo limitado àquele contexto.

Bounded Context "Auth"     → Agente auth_agent
  - Conhece: User, Session, Token, Permission
  - Não conhece: Product, Order, Payment

Bounded Context "Catalog"  → Agente catalog_agent
  - Conhece: Product, Category, Inventory
  - Não conhece: User, Order, Payment

Bounded Context "Orders"   → Agente orders_agent
  - Conhece: Order, OrderItem, Cart
  - Não conhece: User (interno), Product (interno)
  - Conhece: UserID (referência), ProductID (referência)

O ponto sutil é a diferença entre "conhecer" e "referenciar". O agente de Orders não conhece o modelo interno de User (não sabe que tem campo password_hash ou last_login). Mas ele referencia o UserID como chave estrangeira. Essa distinção é fundamental: referências são acoplamento fraco, importações diretas são acoplamento forte.

Na prática, o agente de Orders tem no seu contexto os arquivos de orders/ e as interfaces públicas de auth/ e catalog/ (os contratos). Ele sabe que pode chamar GetUserByID(id) mas não sabe como essa função é implementada internamente.

Linguagem Ubíqua por Agente

Em DDD, cada bounded context tem sua própria linguagem ubíqua: os termos que o time usa para falar sobre aquele domínio. "Checkout" significa uma coisa no contexto de Orders (finalizar pedido) e outra no contexto de Payment (processar cobrança).

Para agentes, isso se traduz em instruções específicas no system prompt. O agente de Orders sabe que "item" significa OrderItem (produto + quantidade + preço no momento da compra). O agente de Catalog sabe que "item" significa Product (entidade com descrição, fotos e estoque). Quando o usuário diz "adicionar item", o orquestrador roteia para o agente certo baseado no contexto da conversa, e cada agente interpreta "item" de acordo com sua linguagem.

Isso evita ambiguidade. Se um agente genérico recebe "adicionar item", ele não sabe se é adicionar ao catálogo ou adicionar ao carrinho. Com agentes separados por bounded context, a ambiguidade desaparece porque cada agente só tem uma interpretação possível dentro do seu domínio.

Context Maps: Como Agentes se Relacionam

Em DDD, Context Maps definem como bounded contexts se relacionam: Customer-Supplier, Shared Kernel, Anti-Corruption Layer, etc. Para agentes, essas relações definem como a coordenação acontece.

Customer-Supplier: o agente de Orders (customer) depende do agente de Catalog (supplier) para obter informações de produto. O Catalog define a interface, o Orders consome. Se o Catalog muda a interface, o Orders precisa se adaptar.

Shared Kernel: os agentes de Orders e Payment compartilham o conceito de "Amount" (valor monetário com moeda). Ambos precisam concordar na representação. Isso vira uma interface compartilhada que ambos os agentes conhecem.

Conformist: o agente de Notification se conforma ao modelo de eventos que outros agentes emitem. Ele não negocia o formato, apenas consome o que recebe. É o agente mais passivo da arquitetura.

No orquestrador, essas relações são implícitas na fase de coordenação. Quando o orquestrador compartilha as análises entre agentes (fase 2 da coordenação), ele está implementando a comunicação definida pelo context map. O agente de Orders recebe a análise do agente de Catalog e sabe quais interfaces estão disponíveis.

Anti-Corruption Layer entre Agentes

Quando dois agentes precisam se comunicar mas têm modelos incompatíveis, uma Anti-Corruption Layer (ACL) traduz entre eles. No código humano, isso é um adapter ou translator. No contexto de agentes, isso é uma instrução no prompt de coordenação.

Por exemplo: o agente de Catalog trabalha com Product{ID, Name, Price, Stock}. O agente de Orders trabalha com OrderItem{ProductID, ProductName, UnitPrice, Quantity}. Quando o orquestrador coordena uma operação de "adicionar ao carrinho", ele precisa traduzir: o Product do Catalog vira um OrderItem no Orders, com mapeamento explícito de campos.

Na implementação, isso acontece na fase de coordenação. O orquestrador inclui no prompt do agente de Orders: "O Catalog expõe Product com campos ID, Name, Price. Mapeie para seu modelo OrderItem usando ProductID=ID, ProductName=Name, UnitPrice=Price". O agente de Orders implementa o mapeamento sem precisar conhecer o modelo interno do Catalog.

Detecção Automática de Contexts

O Agent Spread detecta bounded contexts pela estrutura de pastas, mas a detecção pode ser mais sofisticada analisando o código:

func (o *Orchestrator) looksLikeBoundedContext(path string) bool {
    indicators := []string{
        "domain", "application", "infrastructure",
        "src", "controllers", "services",
    }

    entries, _ := os.ReadDir(path)
    count := 0
    for _, entry := range entries {
        if entry.IsDir() {
            for _, indicator := range indicators {
                if entry.Name() == indicator {
                    count++
                    break
                }
            }
        }
    }

    return count >= 2
}

Se uma pasta tem domain/ + application/ (padrão hexagonal), ou controllers/ + services/ (padrão MVC), provavelmente é um bounded context. A heurística não é perfeita mas funciona para projetos que seguem convenções comuns.

Uma evolução seria analisar imports entre módulos: se dois módulos nunca se importam diretamente (só via interfaces), provavelmente são bounded contexts separados. Se um módulo importa internals de outro, provavelmente deveriam ser o mesmo contexto ou precisam de uma ACL.

Considerações Finais

DDD e multi-agente são complementares de uma forma que não é óbvia à primeira vista. DDD te diz como separar o domínio em contextos coesos. Multi-agente te dá a infraestrutura para enforçar essa separação. Juntos, eles produzem código gerado por IA que respeita fronteiras de domínio naturalmente, sem precisar de revisão humana para corrigir acoplamento.

O que eu quero que você leve deste artigo é que bounded contexts não são só uma ferramenta de organização de código. São uma ferramenta de organização de contexto para IA. Quando você define fronteiras claras entre domínios, está simultaneamente organizando seu código para humanos e limitando o escopo para agentes. É um investimento que paga duas vezes.

E a detecção automática, por mais imperfeita que seja, é melhor que nenhuma detecção. Um sistema que acerta 80% dos bounded contexts automaticamente e permite correção manual dos outros 20% é infinitamente melhor que um sistema que exige configuração manual de 100%.

Links indicativos: