Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

01 - Shared Kernel

#acoplamento#bounded-context#ddd#shared-kernel#versionamento
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione 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:

  1. Inventarie os conceitos duplicados entre os dois contextos, classificando cada um por taxa de mudança e custo de tradução.
  2. Escolha o menor conjunto com maior valor, tipicamente identificadores e um ou dois objetos de valor, e resista à tentação de ampliar.
  3. Extraia o módulo com testes próprios, dono formal e pipeline independente, mesmo que a extração inicial seja cópia controlada.
  4. Defina por escrito o regime de mudança, incluindo semver, janela de deprecação e canal de comunicação entre os times.
  5. Automatize a matriz de compatibilidade no CI antes do primeiro consumo real, e não depois do primeiro incidente.
  6. Publique a primeira versão e migre os dois contextos em deploys coordenados, com o contrato de rollback acordado.
  7. 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.