Shared Kernel

Como compartilhar um subconjunto controlado do modelo de domínio entre bounded contexts para reduzir duplicação sem sacrificar a autonomia das equipes.

Que problema resolve

Em uma arquitetura de microsserviços com múltiplos bounded contexts, cada contexto tem seu próprio modelo de domínio. Mas, às vezes, dois ou mais contextos precisam compartilhar certos conceitos que são idênticos em ambos os lados:

Sem Shared Kernel:
  Order Service                    Shipping Service
  ┌────────────────┐              ┌────────────────┐
  │ Address {      │              │ Address {      │
  │   street       │              │   street       │
  │   city         │              │   city         │
  │   state        │              │   state        │
  │   zipCode      │              │   zipCode      │
  │   country      │              │   country      │
  │   validate()   │              │   validate()   │
  │ }              │              │ }              │
  └────────────────┘              └────────────────┘
  
  Mesma classe duplicada em ambos os serviços
  Se a validação mudar, é preciso mudar nos dois
  Risco de divergência silenciosa

Os problemas são:

  • Duplicação: O mesmo código é mantido em múltiplos lugares
  • Divergência: Com o tempo, as cópias evoluem de forma diferente e se tornam inconsistentes
  • Esforço de sincronização: Cada mudança precisa ser replicada manualmente em todos os contextos
  • Bugs sutis: Uma validação atualizada em um serviço, mas não em outro, causa inconsistências

Como funciona

O Shared Kernel define um subconjunto pequeno e explícito do modelo de domínio que é compartilhado entre dois ou mais bounded contexts. Esse kernel compartilhado é gerenciado como uma biblioteca ou pacote independente, com versionamento próprio.

Com Shared Kernel:
  ┌─────────────────────────────────┐
  │        Shared Kernel            │
  │  (biblioteca versionada)        │
  │                                 │
  │  Address { street, city, ... }  │
  │  Money { amount, currency }     │
  │  DateRange { from, to }         │
  │  OrderStatus enum               │
  └─────────┬───────────┬───────────┘
            │           │
            ▼           ▼
  Order Service    Shipping Service
  (usa v2.1.0)     (usa v2.1.0)

O que incluir no Shared Kernel

IncluirNão incluir
Value Objects compartilhados (Address, Money, DateRange)Entidades com lógica de negócio específica
Enums de estado compartilhados (OrderStatus)Repositórios ou serviços de aplicação
DTOs de eventos de integraçãoLógica de negócio de um único contexto
Interfaces de contratos (API contracts)Modelos de persistência (entidades de BD)
Validações comunsConfiguração específica de infraestrutura

Regras do Shared Kernel

O Shared Kernel funciona sob regras rígidas para evitar que se torne um problema:

  1. Mínimo necessário: Incluir apenas o que realmente é compartilhado entre os contextos
  2. Propriedade compartilhada: Ambas as equipes precisam concordar com qualquer mudança
  3. Versionamento semântico: Mudanças breaking exigem uma nova versão major
  4. Testes compartilhados: O kernel tem seus próprios testes, que ambas as equipes executam
  5. Revisão conjunta: As mudanças no kernel exigem aprovação de todas as equipes consumidoras

Exemplo: Shared Kernel como pacote

shared-kernel/
├── src/
│   ├── value-objects/
│   │   ├── Address.ts
│   │   ├── Money.ts
│   │   └── DateRange.ts
│   ├── events/
│   │   ├── OrderCreatedEvent.ts
│   │   └── PaymentProcessedEvent.ts
│   ├── enums/
│   │   ├── OrderStatus.ts
│   │   └── PaymentMethod.ts
│   └── index.ts
├── tests/
│   ├── Address.test.ts
│   ├── Money.test.ts
│   └── DateRange.test.ts
├── package.json  (version: "2.1.0")
└── CHANGELOG.md

Cada serviço o consome como dependência:

// Order Service - package.json
{
  "dependencies": {
    "@company/shared-kernel": "^2.1.0"
  }
}

// Shipping Service - package.json
{
  "dependencies": {
    "@company/shared-kernel": "^2.1.0"
  }
}

Evolução do Shared Kernel

Versão 1.0.0: Address, Money
Versão 1.1.0: + DateRange (backward compatible)
Versão 2.0.0: Address muda a validação de zipCode (breaking change)
  → Ambas as equipes devem atualizar e testar
  → O deploy é coordenado

Shared Kernel vs outras estratégias

EstratégiaAcoplamentoAutonomiaQuando usar
Shared KernelMédio (controlado)MédiaConceitos idênticos em 2-3 contextos
ACL (Anti-Corruption Layer)BaixoAltaModelos diferentes que precisam de tradução
Copiar e divergirNenhumTotalConceitos parecidos, mas que evoluem de forma diferente
Serviço compartilhadoAltoBaixaFuncionalidade completa compartilhada

Vantagens

  • Elimina duplicação: Um único lugar para o código compartilhado
  • Consistência garantida: Todos os contextos usam a mesma versão do modelo
  • Evolução coordenada: As mudanças se propagam de forma controlada
  • Testing centralizado: Os testes do kernel validam o comportamento compartilhado
  • Contratos explícitos: O kernel define formalmente o que é compartilhado

Trade-offs / Desvantagens

  • Acoplamento entre equipes: As mudanças no kernel exigem coordenação entre as equipes
  • Velocidade de desenvolvimento: Uma equipe pode ficar bloqueada esperando por uma mudança no kernel
  • Risco de crescimento: Se o kernel crescer demais, ele se transforma em um monolito compartilhado
  • Versionamento complexo: Gerenciar versões e compatibilidade entre consumidores exige disciplina
  • Deploy coordenado: Mudanças breaking exigem que todos os consumidores atualizem
  • Governance: Precisa de regras claras sobre quem pode modificar o kernel e como

Quando usar

  • Dois ou mais bounded contexts compartilham value objects idênticos (Address, Money, DateRange)
  • Eventos de integração que precisam ter o mesmo schema no produtor e no consumidor
  • Enums de estado que precisam ser consistentes entre os serviços
  • Quando a duplicação causa bugs frequentes por divergência
  • Equipes que trabalham em domínios próximos e que conseguem se coordenar facilmente

Quando evitar

  • Mais de 3-4 contextos consumidores (a coordenação se torna incontrolável)
  • Quando os conceitos “compartilhados” na verdade têm semânticas diferentes em cada contexto
  • Equipes distribuídas geograficamente com pouca comunicação
  • Quando a autonomia das equipes é mais importante do que evitar duplicação
  • Se o kernel tende a crescer sem controle (sinal de que os bounded contexts estão mal definidos)

Tecnologias e implementações comuns

CategoriaOpções
Distribuiçãonpm packages, Maven artifacts, NuGet packages, Git submodules
VersionamentoSemantic Versioning (SemVer), Conventional Commits
MonorepoNx, Turborepo, Lerna (kernel como pacote interno)
Registronpm registry privado, Artifactory, GitHub Packages
CI/CDPipeline dedicado para o kernel com testes de compatibilidade

Relação com outros padrões

  • DDD (Bounded Context): O Shared Kernel é uma relação entre bounded contexts definida por Eric Evans
  • Anti-Corruption Layer: Alternativa ao Shared Kernel quando os modelos são diferentes
  • Event-Driven Architecture: Os schemas de eventos de integração são candidatos naturais para o kernel
  • Microsserviços: O Shared Kernel deve ser pequeno para não comprometer a independência dos serviços
  • Contratos (API Contracts): Os contratos de API podem viver no Shared Kernel

Próximos passos

O Shared Kernel é uma ferramenta poderosa, mas que exige disciplina. Para entender a alternativa quando os modelos são incompatíveis, veja o Anti-Corruption Layer. Para ver como os eventos de integração se beneficiam do kernel compartilhado, explore Event-Driven Architecture.