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
| Incluir | Nã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ção | Lógica de negócio de um único contexto |
| Interfaces de contratos (API contracts) | Modelos de persistência (entidades de BD) |
| Validações comuns | Configuração específica de infraestrutura |
Regras do Shared Kernel
O Shared Kernel funciona sob regras rígidas para evitar que se torne um problema:
- Mínimo necessário: Incluir apenas o que realmente é compartilhado entre os contextos
- Propriedade compartilhada: Ambas as equipes precisam concordar com qualquer mudança
- Versionamento semântico: Mudanças breaking exigem uma nova versão major
- Testes compartilhados: O kernel tem seus próprios testes, que ambas as equipes executam
- 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égia | Acoplamento | Autonomia | Quando usar |
|---|---|---|---|
| Shared Kernel | Médio (controlado) | Média | Conceitos idênticos em 2-3 contextos |
| ACL (Anti-Corruption Layer) | Baixo | Alta | Modelos diferentes que precisam de tradução |
| Copiar e divergir | Nenhum | Total | Conceitos parecidos, mas que evoluem de forma diferente |
| Serviço compartilhado | Alto | Baixa | Funcionalidade 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
| Categoria | Opções |
|---|---|
| Distribuição | npm packages, Maven artifacts, NuGet packages, Git submodules |
| Versionamento | Semantic Versioning (SemVer), Conventional Commits |
| Monorepo | Nx, Turborepo, Lerna (kernel como pacote interno) |
| Registro | npm registry privado, Artifactory, GitHub Packages |
| CI/CD | Pipeline 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.