O Schema Registry no Node.js centraliza contratos usados por produtores e consumidores de eventos. Em arquiteturas com Kafka, várias aplicações publicam e leem mensagens ao longo do tempo. Sem controle de schema, uma mudança simples — renomear um campo, alterar tipo ou remover propriedade — pode quebrar consumidores sem aviso.
Um registry armazena versões de schemas, verifica compatibilidade e entrega IDs usados na serialização. Em vez de colocar a definição completa em cada mensagem, o produtor registra o schema, recebe um identificador e serializa o payload em um formato como Avro, Protobuf ou JSON Schema.
Neste guia, você aprenderá a integrar Node.js com Schema Registry, registrar schemas, serializar mensagens, configurar compatibilidade, testar evolução, trabalhar com KafkaJS e evitar breaking changes.
O que é Schema Registry?
A documentação oficial do Confluent Schema Registry descreve um repositório central para schemas Avro, Protobuf e JSON Schema. O serviço fornece API REST, versionamento, validação de compatibilidade e suporte a serializers e deserializers.
O fluxo básico é:
- definir o schema;
- registrar no subject;
- receber um schema ID;
- serializar a mensagem;
- publicar no Kafka;
- o consumidor lê o ID;
- busca ou usa cache do schema;
- desserializa o payload.
Por que não usar JSON livre?
JSON sem contrato é simples no início, mas deixa problemas para produção:
- campos obrigatórios desaparecem;
- tipos mudam silenciosamente;
- consumidores interpretam versões diferentes;
- documentação fica desatualizada;
- breaking changes chegam ao tópico;
- validação é duplicada em cada serviço.
JSON Schema pode resolver parte disso, mas o registry adiciona versionamento e compatibilidade centralizados.
Integração com KafkaJS
Uma combinação comum usa KafkaJS e o cliente de Schema Registry:
npm install kafkajs @kafkajs/confluent-schema-registryimport { Kafka } from 'kafkajs';
import { SchemaRegistry } from '@kafkajs/confluent-schema-registry';
const kafka = new Kafka({
clientId: 'orders-api',
brokers: process.env.KAFKA_BROKERS.split(',')
});
const registry = new SchemaRegistry({
host: process.env.SCHEMA_REGISTRY_URL
});Para produtores e consumidores, consulte Kafka com Node.js.
Primeiro schema Avro
{
"type": "record",
"name": "OrderCreated",
"namespace": "com.codigofacil.orders",
"fields": [
{ "name": "orderId", "type": "string" },
{ "name": "customerId", "type": "string" },
{ "name": "totalCents", "type": "long" },
{ "name": "occurredAt", "type": "string" }
]
}O schema define estrutura e tipos. O produtor não pode enviar uma string em totalCents quando o contrato exige long.
Registrando o schema
import { SchemaType } from '@kafkajs/confluent-schema-registry';
const schema = JSON.stringify({
type: 'record',
name: 'OrderCreated',
namespace: 'com.codigofacil.orders',
fields: [
{ name: 'orderId', type: 'string' },
{ name: 'customerId', type: 'string' },
{ name: 'totalCents', type: 'long' },
{ name: 'occurredAt', type: 'string' }
]
});
const { id } = await registry.register({
type: SchemaType.AVRO,
schema
});Em produção, schemas normalmente são registrados por pipeline ou ferramenta de governança, não a cada request.
Serializando o evento
const payload = {
orderId: 'order-42',
customerId: 'customer-7',
totalCents: 15990,
occurredAt: new Date().toISOString()
};
const encoded = await registry.encode(id, payload);Depois publique os bytes:
const producer = kafka.producer();
await producer.connect();
await producer.send({
topic: 'orders.created.v1',
messages: [
{
key: payload.orderId,
value: encoded
}
]
});Desserializando no consumidor
const consumer = kafka.consumer({
groupId: 'billing-orders-created'
});
await consumer.connect();
await consumer.subscribe({
topic: 'orders.created.v1'
});
await consumer.run({
eachMessage: async ({ message }) => {
const event = await registry.decode(message.value);
await handleOrderCreated(event);
}
});O cliente lê o schema ID do wire format e usa cache para evitar chamadas repetidas ao registry.
Subjects
Um subject agrupa versões relacionadas. Estratégias comuns:
- TopicNameStrategy;
- RecordNameStrategy;
- TopicRecordNameStrategy.
A escolha afeta reutilização e compatibilidade. Para um schema por tópico, TopicNameStrategy é simples. Para vários tipos no mesmo tópico, RecordNameStrategy pode ser mais adequado.
Compatibilidade backward
Backward significa que o novo schema consegue ler dados produzidos pelo schema anterior. Um exemplo compatível é adicionar campo com default:
{
"name": "currency",
"type": "string",
"default": "BRL"
}Consumidores novos leem eventos antigos e usam o valor padrão.
Compatibilidade forward
Forward significa que consumidores antigos conseguem ler dados produzidos com o novo schema. Adicionar um campo geralmente é compatível porque leitores antigos ignoram o que não conhecem, dependendo do formato e das regras.
Compatibilidade full
Full combina backward e forward. É mais restritiva, mas reduz risco quando produtores e consumidores são implantados em ordens diferentes.
Transitive compatibility
Modos transitive comparam com todas as versões anteriores, não apenas a última. Isso impede que uma sequência de mudanças aparentemente compatíveis quebre consumidores muito antigos ainda suportados.
Breaking changes
Mudanças perigosas incluem:
- remover campo sem planejamento;
- trocar string por número;
- renomear propriedade;
- alterar namespace ou record name;
- reutilizar campo com outra semântica;
- mudar enum sem compatibilidade;
- remover default necessário.
Quando o contrato realmente precisa quebrar, crie novo tópico ou versão explícita.
Schema como API
Trate schemas como APIs públicas. Eles precisam de:
- revisão;
- versionamento;
- documentação;
- testes;
- depreciação;
- responsável definido;
- política de compatibilidade.
Veja Semantic Versioning no Node.js para pensar em impacto de mudanças.
Validação antes do deploy
O pipeline deve verificar compatibilidade antes de registrar:
npm run schema:check
npm run test
npm run buildA API do registry oferece endpoints de compatibility. A ferramenta usada deve comparar o schema candidato com o subject correto.
Separando registro e runtime
Evite registrar schemas automaticamente em toda inicialização de produção. Isso pode criar versões acidentais e exige permissão de escrita no runtime. Um modelo mais seguro:
- CI valida;
- release registra;
- aplicação recebe ID ou busca por subject;
- runtime possui leitura limitada.
Cache
Clientes mantêm schemas em memória. Defina limites e monitore falhas. Se o registry estiver temporariamente indisponível, mensagens com schemas já em cache podem continuar sendo processadas, mas novos IDs podem falhar.
Disponibilidade
Schema Registry vira dependência crítica para novos schemas e consumidores que ainda não têm cache. Em produção:
- execute múltiplas instâncias;
- use health checks;
- proteja storage;
- configure TLS;
- monitore latência;
- planeje backup e recuperação.
Autenticação
const registry = new SchemaRegistry({
host: process.env.SCHEMA_REGISTRY_URL,
auth: {
username: process.env.SCHEMA_REGISTRY_USER,
password: process.env.SCHEMA_REGISTRY_PASSWORD
}
});Use secret manager e não registre credenciais. Consulte Gestão de Segredos no Node.js.
Avro, Protobuf ou JSON Schema?
- Avro: compacto, bom suporte a evolução e ecossistema Kafka;
- Protobuf: tipos e geração de código fortes;
- JSON Schema: formato legível e alinhado a APIs JSON.
A escolha depende de consumidores, linguagem e governança. O próximo artigo aprofunda Avro.
Mensagens com metadata
Não coloque apenas dados de negócio. Inclua envelope:
{
"eventId": "evt-123",
"eventType": "order.created",
"eventVersion": 1,
"occurredAt": "2026-09-14T12:00:00Z",
"correlationId": "corr-42",
"data": {}
}O envelope também deve ter schema. IDs ajudam idempotência e rastreamento. Veja Idempotência em APIs Node.js.
Outbox
O schema não resolve consistência entre banco e Kafka. Use Outbox Pattern para gravar evento e alteração de negócio na mesma transação. Consulte Outbox Pattern no Node.js.
Testes de contrato
Inclua fixtures antigas:
test('consumidor lê versão anterior', async () => {
const oldMessage = await loadFixture('order-created-v1.bin');
const event = await registry.decode(oldMessage);
assert.equal(event.currency ?? 'BRL', 'BRL');
});Teste também um produtor novo contra consumidor antigo quando o modo exige forward compatibility.
Testcontainers
Suba Kafka e Schema Registry em integração. Isso valida wire format, subjects e autenticação. Veja Testcontainers no Node.js.
Observabilidade
Meça:
- latência de encode e decode;
- cache hit;
- falhas de registry;
- schema IDs desconhecidos;
- incompatibilidades;
- versões por subject;
- erros de validação.
Erros comuns
- JSON sem contrato: consumidores quebram.
- Registrar no startup: versões acidentais surgem.
- Compatibilidade none: qualquer breaking entra.
- Alterar sem default: eventos antigos falham.
- Runtime com escrita ampla: risco de governança.
- Ignorar cache: registry vira gargalo.
- Sem teste de fixtures: compatibilidade teórica.
- Schema sem owner: evolução fica caótica.
Conclusão
O Schema Registry no Node.js transforma mensagens Kafka em contratos versionados. Produtores serializam com um schema conhecido e consumidores validam evolução antes que mudanças incompatíveis cheguem à produção.
Defina subjects, compatibilidade e ownership desde o início. Registre schemas no pipeline, teste versões antigas e combine com idempotência e outbox. Assim, eventos deixam de ser JSON informal e se tornam APIs duráveis entre serviços.



