Shared kernel: quando compartilhar modelo entre contextos

1. Fundamentos do Shared Kernel no DDD

O Shared Kernel é um padrão de mapeamento de contextos (Context Mapping) do Domain-Driven Design que define um subconjunto compartilhado do modelo de domínio entre dois ou mais bounded contexts. Diferentemente de outros padrões de integração, o Shared Kernel pressupõe que o acoplamento é não apenas aceitável, mas desejável — desde que seja explícito, controlado e limitado a um núcleo semanticamente estável.

Eric Evans introduziu o conceito em seu livro "Domain-Driven Design" (2003) como uma alternativa ao Customer/Supplier (onde um contexto dita as regras) e ao Conformist (onde um contexto simplesmente se adapta). No Shared Kernel, ambas as equipes colaboram para definir e evoluir o modelo compartilhado, mantendo autonomia sobre suas extensões específicas.

A principal diferença para o Open Host Service (OHS) é que este expõe funcionalidades via interface publicada, enquanto o Shared Kernel compartilha o próprio modelo — entidades, value objects, interfaces de repositório e regras de negócio centrais.

2. Critérios para Decisão de Compartilhamento

Nem todo conceito merece entrar no Shared Kernel. A decisão deve basear-se em três critérios fundamentais:

Alta coesão semântica: os contextos devem usar o mesmo vocabulário e as mesmas regras para o conceito compartilhado. Se "Usuário" significa uma coisa no Contexto A e outra no Contexto B, o Shared Kernel não é adequado.

Baixa volatilidade: modelos que mudam frequentemente em um dos lados geram retrabalho no outro. Apenas conceitos maduros e estáveis devem ser compartilhados.

Custo de tradução vs. custo de acoplamento: implementar um Anti-corruption Layer (ACL) para traduzir entre contextos tem custo de desenvolvimento e manutenção. Se esse custo supera o custo do acoplamento direto, o Shared Kernel é a melhor escolha.

Cenários típicos incluem identidade de usuário (compartilhada entre autenticação e autorização), catálogo de produtos (entre vendas e logística) e moeda/câmbio (entre vendas e contabilidade).

3. Estrutura e Governança do Shared Kernel

O Shared Kernel deve conter apenas o subconjunto mínimo viável: entidades centrais, value objects imutáveis e interfaces de repositório. Implementações concretas e detalhes de infraestrutura ficam fora.

shared-kernel/
  identity/
    User.java
    UserId.java
    UserRepository.java
  catalog/
    ProductId.java
    Money.java
    Currency.java

A governança exige um contrato claro entre as equipes:

  • Responsabilidades: quem propõe mudanças, quem revisa, quem aprova
  • Versionamento: semântico (major.minor.patch) com changelog obrigatório
  • Processo de mudança: pull request revisado por ambas as equipes, testes automáticos de compatibilidade

Testes de contrato (contract tests) garantem que o kernel permanece compatível com todos os consumidores. Ferramentas como Pact ou testes de integração dedicados são recomendadas.

4. Versionamento e Evolução do Modelo Compartilhado

O versionamento semântico é essencial para o Shared Kernel. Mudanças breaking (major version) exigem coordenação entre equipes e período de migração.

# shared-kernel v2.1.0
# breaking: User.rename() agora exige UserId explícito
# migration: User.rename(String name) → User.rename(UserId, String name)

Estratégias de compatibilidade retroativa incluem:

  1. Métodos deprecated: manter o método antigo por N versões com anotação @Deprecated
  2. Adaptadores de migração: classes que traduzem entre versões antiga e nova
  3. Event versioning: evoluir schemas de eventos sem quebrar consumidores, usando múltiplas versões simultâneas

O ideal é nunca remover um método sem antes garantir que nenhum consumidor o utiliza. Ferramentas de análise de dependências ajudam a rastrear o uso.

5. Integração com Outros Padrões de Context Mapping

O Shared Kernel não existe isoladamente. Ele frequentemente coexiste com outros padrões:

[Contexto A] ← shared kernel → [Contexto B]
     ↓                              ↓
[Anti-corruption]            [Open Host Service]
     ↓                              ↓
[Sistema Legado]              [Contexto C]

Neste exemplo, os Contextos A e B compartilham o kernel, enquanto A protege-se de um sistema legado via ACL, e B expõe funcionalidades para C via OHS.

O Event Storming é particularmente útil para identificar candidatos ao Shared Kernel: eventos, comandos e agregados que aparecem consistentemente em múltiplos contextos merecem análise de compartilhamento.

6. Riscos e Mitigações

Acoplamento excessivo: o maior risco é o kernel crescer descontroladamente, tornando-se um "big ball of mud". Mitigação: definir limites claros no início e revisar periodicamente o escopo.

Conflitos de agenda: equipes com prioridades diferentes podem bloquear evoluções necessárias. Mitigação: processo de revisão com prazo máximo e mediação técnica.

Degradação do modelo: inclusão de conceitos periféricos que não pertencem ao núcleo. Mitigação: critérios rigorosos de entrada e refatoração contínua.

Estratégia de saída: quando o Shared Kernel se torna inviável, a refatoração para Customer/Supplier (um contexto dita as regras) ou Separat Ways (cada contexto mantém seu próprio modelo com sincronização) são alternativas.

7. Casos Práticos e Padrões de Implementação

Exemplo 1: Autenticação e Autorização

Compartilhar User e Role entre contextos de Autenticação e Autorização faz sentido porque o conceito de "quem é o usuário" e "quais papéis ele possui" é semanticamente idêntico em ambos.

// shared-kernel/src/main/java/com/empresa/shared/identity/User.java
public class User {
    private UserId id;
    private String email;
    private Set<Role> roles;

    public boolean hasRole(Role role) {
        return roles.contains(role);
    }
}

Exemplo 2: Vendas e Contabilidade

Compartilhar Money e Currency evita erros de conversão e inconsistências contábeis.

// shared-kernel/src/main/java/com/empresa/shared/money/Money.java
public class Money {
    private BigDecimal amount;
    private Currency currency;

    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new CurrencyMismatchException();
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

Checklist para decidir se um modelo deve entrar no Shared Kernel:

  1. O conceito é usado por dois ou mais contextos?
  2. O significado é idêntico em todos os contextos?
  3. O modelo é estável (mudanças raras)?
  4. O custo de tradução via ACL é maior que o custo do acoplamento?
  5. As equipes concordam com a governança?

Se todas as respostas forem "sim", o Shared Kernel é a escolha certa. Caso contrário, considere padrões alternativos como ACL, OHS ou Separat Ways.

Referências