01 - Shared Kernel
video: https://youtu.be/... no frontmatter da nota.Shared Kernel
O Shared Kernel é a porção do modelo que dois contextos delimitados decidem compartilhar de forma explícita: objetos de valor, entidades-base, enums de negócio, schemas de eventos ou tipos identificadores que ambos usam. Ele não é o repositório de código utilitário da empresa, e sim um subconjunto deliberado do modelo com significado de domínio, mantido em conjunto e com regime de mudança próprio. A relevância em produção vem do trade-off que materializa: compartilhar elimina o custo de duplicar e traduzir conceitos estáveis, ao preço de acoplar o ciclo de release dos dois contextos. Times que adotam o kernel sem governança descobrem tarde demais que uma alteração simples em um tipo comum quebra builds e deploys de várias squads ao mesmo tempo. Times que evitam o kernel em todo caso pagam tradução repetida para conceitos que nunca mudam. A decisão correta nasce do ritmo de mudança do conceito e da maturidade de coordenação entre os times envolvidos.
Conceito e escopo do kernel compartilhado
O kernel é um acordo de propriedade mútua: os dois contextos são co-autores do código, e nenhum dos dois muda sem conversar com o outro. A fronteira do kernel é desenhada como um módulo separado, com dono formal, teste próprio e ciclo de versionamento independente, ainda que acoplado aos consumidores. Isso o diferencia de uma biblioteca genérica, que pode crescer livremente porque não carrega semântica de negócio.
Os conteúdos típicos de um kernel bem desenhado incluem:
- Tipos identificadores de domínio, como OrderId e CustomerId, que garantem que um identificador de pedido não seja confundido com um identificador de cliente em nenhum contexto.
- Objetos de valor fundamentais, como Money e Periodo, cujo comportamento aritmético e de comparação é igual em qualquer contexto e cuja duplicação geraria divergência de regras.
- Schemas de eventos de integração estáveis, que definem o formato do contrato assíncrono entre os contextos.
- Enums de negócio de baixa volatilidade, como StatusDePedido, compartilhados quando os dois contextos reagem aos mesmos estados.
- Utilitários que embutem regra de domínio, como validadores de documento fiscal, e não helpers genéricos de string ou data.
O critério que atravessa todos os itens é estabilidade com valor compartilhado alto. Conceito que muda a cada sprint não é candidato; conceito que nunca muda e é escrito de forma diferente nos dois lados é candidato forte.
Vale distinguir o kernel de vizinhos comuns que frequentemente são confundidos com ele. A camada de utilitários da empresa não é kernel porque não contém modelo, apenas ferramenta. A base de código de um contexto importada pelo outro não é kernel porque não há propriedade mútua, há dependência unilateral, que é exatamente o desenho de Customer-Supplier. O schema de integração mantido por um dos lados e apenas consumido pelo outro tampouco é kernel; ele só se torna kernel quando os dois times revisam e aprovam mudanças no mesmo arquivo, com igual peso de veto.
O problema que resolve
Sem kernel, dois contextos que usam o mesmo conceito têm duas escolhas, e ambas têm custo. Duplicar o conceito gera drift: o objeto de valor Money do contexto de cobrança aprende a arredondar de uma forma, o do contexto de relatório aprende de outra, e os totais divergem em produção sem nenhum erro logado. Traduzir sempre gera atrito: cada integração carrega um adaptador para converter Money de um lado no Money do outro, um custo permanente por um conceito cujo comportamento é idêntico nos dois mundos.
O kernel formaliza o meio-termo: compartilhe o núcleo estável, traduza o resto. O ganho aparece em auditoria e em correção de bug; quando a regra de arredondamento muda por exigência regulatória, a mudança acontece em um lugar, e os dois contextos recebem a nova versão com revisão conjunta. O mesmo bug em mundo duplicado é corrigido duas vezes, com risco de que uma correção esqueça o segundo ponto.
Mecânica do acoplamento de release
O custo central do kernel é o acoplamento de release, e ele precisa ser entendido mecanicamente. Quando o contexto de cobrança precisa de uma mudança no Money, o kernel é versionado, publicado e então os dois contextos dependem de atualizar a dependência, reconstruir, testar e implantar. Se o contexto de relatório não acompanhar, o ambiente passa a executar duas versões do mesmo conceito, e a divergência volta pelo backstage. A sequência de release coordenado é o padrão operacional que faz esse acoplamento funcionar sem incidente.
# Ciclo de release coordenado de um shared kernel
kernel:
artefato: "empresa-kernel-dominio"
versionamento: "semver estrito"
regras:
- "mudança de comportamento em regra de negócio exige major"
- "adição de campo opcional em schema exige minor"
- "correção de bug que altera resultado exige major e changelog"
processo:
- passo: "proposta de mudança abre issue com impacto nos consumidores"
- passo: "pipeline roda testes de contrato contra os dois contextos"
- passo: "publica nova versão sem apagar as antigas"
- passo: "consumidores migram na janela acordada"
- passo: "versão antiga entra em deprecação com prazo definido"
Em monorepo, o kernel é um pacote interno importado por ambos os contextos, e o CI impede merge que quebre qualquer um deles. Em repositórios separados, o kernel é um artefato em registry privado, e a versão usada por cada contexto fica explícita no manifest. As duas formas exigem a mesma disciplina de versão, o que muda é apenas onde a fricção aparece: no monorepo a fricção é de build, no multi-repo a fricção é de publicação.
Para mudanças incompatíveis, o padrão expand-contract mantém os deploys desacoplados mesmo com kernel compartilhado. O kernel publica a nova versão sem remover a antiga, os dois contextos migram em momentos diferentes dentro da janela acordada, e a versão antiga é removida só quando a métrica de consumo chega a zero. O caminho proibido é a alteração direta na versão em uso, que força os dois contextos a implantar na mesma janela e transforma qualquer incidente em roll-back coordenado de duas equipes.
O que pertence ao kernel e o que não pertence
A tabela a seguir aplica o critério de estabilidade e valor compartilhado a candidatos comuns, com base em erros observados em produções reais.
| Candidato a compartilhar | Decisão usual | Justificativa operacional |
|---|---|---|
| Objeto de valor Money | Compartilhar | Regra única de precisão e arredondamento, volatilidade baixa |
| Tipo OrderId | Compartilhar | Evita conversões e troca de tipos primitivos nas fronteiras |
| Schema de evento de integração | Compartilhar com governança | É contrato, e contrato duplicado é contrato que diverge |
| Entidade agregada Pedido | Não compartilhar | Cada contexto precisa de visões diferentes do mesmo conceito |
| Regras de precificação | Não compartilhar | Volatilidade alta e diferenciação competitiva entre contextos |
| Helper de data e string | Não compartilhar | Sem semântica de domínio, é biblioteca genérica e não kernel |
| Enum de status de ciclo de vida | Compartilhar com cautela | Só se os dois contextos reagem de fato aos mesmos estados |
A regra de decisão que se extrai da tabela: o kernel carrega identidade e cálculo, não política. Identidade, como identificadores e schemas, é estável por natureza. Cálculo, como Money, é estável porque a aritmética não muda com o mercado. Política, como precificação, regra de crédito e fluxo de aprovação, muda com o negócio e precisa permanecer em cada contexto, onde a linguagem ubíqua local governa.
Versionamento e regime de mudança
O versionamento semântico do kernel não é burocracia, é o mecanismo que permite aos consumidores decidir quando acompanhar. Mudanças incompatíveis exigem versão major, e a versão anterior permanece publicada durante a janela de deprecação acordada entre os times. O erro clássico é tratar o kernel como dependência que pode ser atualizada sem aviso; nesse regime, o primeiro incidente de incompatibilidade destrói a confiança que sustenta o acordo.
O teste de compatibilidade é o que transforma o acordo em mecanismo verificável. O pipeline do kernel roda, além dos próprios testes de unidade, uma matriz que compila e testa os consumidores contra a nova versão candidata. Com essa matriz, uma mudança que quebra o contexto de relatório é detectada no CI do kernel, minutos depois do push, e não no deploy de produção semanas depois.
# Matriz de compatibilidade no CI do kernel
pipeline_kernel:
estagios:
- "testes de unidade do kernel"
- "verificação de compatibilidade de schema do evento"
- "build dos consumidores contra versão candidata"
consumidores:
- contexto: "cobranca"
pipeline: "ci-cobranca.yml"
exigencia: "build e testes verdes"
- contexto: "relatorio"
pipeline: "ci-relatorio.yml"
exigencia: "build e testes verdes"
regra: "qualquer consumidor vermelho bloqueia a publicação"
Aplicação prática: passo a passo
A adoção segura do kernel segue uma sequência que limita o risco em cada etapa:
- Inventarie os conceitos duplicados entre os dois contextos, classificando cada um por taxa de mudança e custo de tradução.
- Escolha o menor conjunto com maior valor, tipicamente identificadores e um ou dois objetos de valor, e resista à tentação de ampliar.
- Extraia o módulo com testes próprios, dono formal e pipeline independente, mesmo que a extração inicial seja cópia controlada.
- Defina por escrito o regime de mudança, incluindo semver, janela de deprecação e canal de comunicação entre os times.
- Automatize a matriz de compatibilidade no CI antes do primeiro consumo real, e não depois do primeiro incidente.
- Publique a primeira versão e migre os dois contextos em deploys coordenados, com o contrato de rollback acordado.
- Revise o escopo do kernel a cada trimestre, removendo o que deixou de ser estável e rejeitando o que não passou no critério.
A ordem dos passos importa tanto quanto o conteúdo deles. Começar pela automação da matriz de compatibilidade, antes do primeiro consumo real, custa horas e evita o cenário em que o primeiro incidente de incompatibilidade destrói o acordo entre os times. Começar com escopo grande, por exemplo compartilhando o agregado de pedido inteiro, produz o pior dos dois mundos: o custo do acoplamento de release e o drift de qualquer forma, porque cada contexto precisa de visões diferentes da mesma entidade.
Quando evitar
O kernel é a estratégia de integração de maior acoplamento estrutural, e há situações em que qualquer outra opção é melhor. Times sem canal de coordenação funcional, por exemplo quando um dos contextos é de outra empresa ou de outra diretoria sem contato regular, não conseguem sustentar a propriedade mútua, e o kernel degenera em dependência unilateral. Conceitos em evolução ativa, como um modelo de crédito em reformulação, impedem o ritmo de release que o kernel exige. Contextos com ciclos de deploy muito distintos, como um monolito mensal e um serviço de deploy diário, transformam cada mudança do kernel em gargalo do mais lento.
Um caso frequente é a extração de um monolito para serviços, em que o código comum é tentadoramente transformado em kernel para acelerar a separação. Essa decisão congela no kernel o que antes era detalhe interno e agora é modelo vivo de dois contextos diferentes. A alternativa mais segura no início da separação é duplicar de forma deliberada, permitindo que os modelos diverjam, e compartilhar só o que sobrevive ao teste de estabilidade depois de alguns meses de evolução independente.
| Situação | Alternativa ao kernel | Motivo |
|---|---|---|
| Time upstream sem compromisso de coordenação | Customer-Supplier ou ACL | Expectativa informal deixa de ser sustentável |
| Conceito de domínio em alta volatilidade | Tradução por ACL | Acoplamento de release travaria a evolução |
| Contextos de empresas diferentes | Contrato versionado e ACL | Propriedade mútua é inviável entre organizações |
| Necessidade é apenas técnica, sem domínio | Biblioteca genérica comum | Kernel com utilitário sem semântica infla o acoplamento |
| Aparência de igualdade, semânticas distintas | Duplicação deliberada | Igualdade falsa gera drift silencioso e correções duplas |
Erros comuns e boas práticas
| Sintoma em produção | Causa raiz | Mitigação |
|---|---|---|
| Deploy de um contexto quebra o outro | Mudança no kernel sem matriz de compatibilidade | CI compila e testa todos os consumidores |
| Regras de cálculo divergem entre sistemas | Conceito duplicado fora do kernel ou bypass dele | Auditar o consumo e eliminar cópias locais |
| Kernel cresce sem controle e vira dependência universal | Escopo sem critério de entrada | Regra escrita: identidade e cálculo, não política |
| Time trava release esperando aprovação lenta | Governança sem SLA definido | Acordo de tempo máximo para revisão de mudança |
| Versão antiga de kernel em produção indefinidamente | Deprecation sem prazo nem dono | Janela acordada com métrica de consumo por versão |
A boa prática que sustenta todas as outras é escrever o acordo. O kernel é antes de tudo um acordo social entre times, e o repositório apenas materializa esse acordo em código. O documento curto que define escopo, dono, regime de versão e janela de deprecação vale mais do que qualquer decisão de estrutura de pastas. Quando um terceiro contexto pede entrada no kernel, a decisão é dos donos atuais, avaliando se o novo consumidor amplia o valor ou apenas amplia o raio de explosão. Kernel com muitos consumidores deixa de ser acordo bilateral e vira infraestrutura de domínio, com custo de governança proporcional, e essa transição precisa ser consciente, nunca cumulativa por acúmulo silencioso de dependências.