Nossa Super Autoestrada de Dados

Um Guia para Viajar com Kafka

Bem-vindo(a) à nossa central de comunicação! Descubra como o Kafka funciona como uma autoestrada de dados eficiente para toda a nossa empresa.

Comece a Viagem Documentação Técnica

Por Onde Começar: Entendendo o Básico

Antes de pegar a estrada, vamos conhecer alguns termos chave do nosso sistema de transporte:

Kafka

A Super Autoestrada de dados em si.
Plataforma de streaming de eventos distribuída.

Evento

Um pacote sendo transportado.
Registro de algo que aconteceu (ex: "Pedido Criado").

Topic (Tópico)

Uma faixa específica da autoestrada para um tipo de carga.
Um fluxo nomeado e ordenado de eventos/mensagens.

Producer (Produtor)

Quem envia o pacote pela autoestrada (ex: sistema de vendas).
Aplicação que publica eventos em um tópico Kafka.

Consumer (Consumidor)

Quem recebe e processa o pacote (ex: sistema de análise).
Aplicação que se inscreve em tópicos e processa os eventos recebidos.

Schema (Esquema)

O formato padrão do pacote/contêiner (o "contrato").
Define a estrutura dos dados dentro de um evento.

Schema Registry (SR)

O DETRAN dos dados / Cartório de regras.
Serviço que armazena, versiona e valida os esquemas (formatos) dos eventos.

Backstage

O mapa/catálogo oficial da cidade e da autoestrada.
Portal interno que cataloga nossos serviços, eventos e documentação.

Eventos vs Mensageria: Entendendo a Diferença

Antes de mergulharmos nos padrões técnicos, é crucial entender a distinção fundamental entre eventos e mensagens. Embora sejam possam ser confundidos, eles servem a propósitos diferentes.

Característica Eventos Mensagens
Natureza Como um carro de notícias que passa anunciando "Aconteceu!". Como um caminhão de entregas com carga definida.
Expectativa Apenas notifica e segue seu caminho - quem quiser que reaja. Tem contrato de entrega e receptor específico.
Conteúdo Carrega só o essencial: "Pedido #123 foi criado!". Transporta a carga completa: todos os dados do pedido.
Exemplo Notificação rápida: "Cliente atualizou seu cadastro" Payload completo com todos os dados atualizados

Eventos são veículos ágeis que apenas notificam ocorrências, enquanto mensagens são transportadores comprometidos com a entrega completa de dados. Os eventos não se preocupam com quem vai reagir à sua passagem, já as mensagens têm destino e expectativas bem definidas.

Em nossa Autoestrada do Kafka temos temos o propósito de trafegar exclusivamente Eventos.

Para uma comparação mais aprofundada, consulte a documentação da Microsoft: Comparar os serviços de mensagens do Azure.

Documentação Técnica

As Leis de Trânsito: Nossos Princípios Fundamentais

Para que nossa autoestrada funcione bem para todos, temos algumas regras de ouro:

Veículos Leves e Rápidos

Mantenha seus eventos pequenos (idealmente até 64 KB).

Por quê?

Pacotes menores viajam mais rápido, consomem menos "combustível" (recursos) e evitam congestionamentos na rede. É como enviar várias cartas rápidas em vez de um caminhão pesado e lento.

Evolução Sem Quebras

Atualizações pequenas (MINOR/PATCH no SemVer) nunca devem "quebrar" quem já está na estrada.

Por quê?

Imagine que mudamos a cor de um sinal de trânsito sem avisar – causaria o caos! Adicionar informações novas (campos opcionais) é como adicionar uma placa nova, não atrapalha quem já conhece o caminho.

Rastro e Imutabilidade

Tudo fica registrado e não pode ser alterado depois de enviado.

Por quê?

Como um registro de entregas confiável. O histórico garante que sempre podemos verificar o que aconteceu, quem enviou o quê e quando. Isso é crucial para auditoria e segurança.

Auto-Documentado

Cada tipo de evento (pacote) vem com seu próprio manual.

Por quê?

Facilita para qualquer um entender o que aquele pacote contém e para que serve, sem precisar perguntar. Usamos o Backstage para manter esse catálogo organizado e acessível.

Monitoramento Constante

Todos os veículos e postos de serviço têm painéis de controle visíveis.

Por quê?

Precisamos saber se há congestionamentos (lag), se os pacotes estão muito grandes ou se há erros nas entregas. A observabilidade é padrão em todos os serviços.

Projetando Nossos Veículos: Como Criar um Evento Perfeito

Agora, vamos ver como construir nossas "mensagens" (eventos) da maneira correta.

O Contêiner Padrão: Nosso Envelope Universal (Proto)

Regra Importante: Em produção, usamos Protobuf, não JSON, para nossos eventos!

Pense no EventEnvelope como a caixa de envio padrão que todos devem usar. Ela tem compartimentos específicos para informações essenciais que acompanham qualquer carga útil (o payload).

Por quê Protobuf? É como usar um contêiner super leve e compacto (binário) em vez de uma caixa grande e cheia de ar (texto como JSON). Isso economiza espaço e acelera o transporte e o processamento.

event_id O código de rastreio único do pacote.
timestamp O carimbo de data/hora de quando o pacote foi criado.
version A versão do conteúdo (payload) dentro do pacote.
correlation_id Um ID de rastreamento que conecta este pacote a outros relacionados.
source Quem enviou o pacote (ex: "serviço-de-pedidos").
payload A carga útil, o conteúdo principal do pacote, em formato Protobuf binário.
api_url (Opcional) Um link para obter mais detalhes sobre o evento.
tenant_id (Opcional) Identifica para qual "cliente" este evento pertence.

Definição do Envelope em Protobuf

// Localização: contracts/_shared/envelope/v1/envelope.proto
syntax = "proto3";
package nstech.nsportal.shared; // Nosso "endereço" organizacional
import "google/protobuf/timestamp.proto"; // Usando um tipo padrão para data/hora

// Nossa caixa de envio padrão
message EventEnvelope {
  string                     event_id       = 1; // Identificador único (UUID v7)
  google.protobuf.Timestamp  timestamp      = 2; // Quando o evento foi criado
  string                     version        = 3; // Versão SemVer do payload (ex: "1.0.0")
  string                     correlation_id = 4; // Para rastrear a jornada completa
  string                     source         = 5; // Sistema de origem (ex: "pedidos-api")
  bytes                      payload        = 6; // A carga útil binária (Protobuf)
  string                     api_url        = 7; // Opcional: URL para mais detalhes
  string                     tenant_id      = 8; // Opcional: Identificador do cliente/tenant
}

Organização dos Contratos

contracts/
├─ _shared/              # Projetos compartilhados por todos
│  └─ envelope/
│     └─ v1/             # Versão 1 do nosso envelope padrão
│        ├─ envelope.proto  # O projeto do envelope
│        ├─ catalog-info.yaml # Info para o catálogo Backstage
│        └─ README.md       # Documentação
└─ sales/                # Projetos da área de Vendas
   └─ order-created/      # Evento "Pedido Criado"
      └─ v1/             # Versão 1 deste evento
         ├─ event.proto     # O projeto do payload (conteúdo)
         ├─ catalog-info.yaml # Info para o Backstage
         └─ README.md       # Documentação
         └─ sample.json   # Exemplo de dados (útil para testes)

O Endereço e a Faixa Certa: Chave e Particionamento

Chave (Key)

Pense na chave como o endereço principal do pacote (ID do pedido, ID do cliente, etc.). Ela ajuda a garantir que todos os pacotes relacionados a um mesmo "assunto" (ex: mesmo pedido) peguem sempre a mesma faixa na autoestrada.

Por quê?

Isso é crucial para quem precisa processar os eventos em ordem para um mesmo cliente ou pedido. Garante que a "história" daquele pedido seja contada na sequência correta.

Compressão

Sempre que enviamos um pacote (produzimos um evento), usamos compressão (zstd).

Por quê?

É como "embalar a vácuo" o pacote antes de enviar. Ele ocupa menos espaço na autoestrada (rede) e no armazém (disco), tornando tudo mais eficiente.

Partição 0
Partição 1
Partição 2

Leve e Rápido: Validando o Tamanho da Carga

Já falamos que pacotes pequenos são melhores. Temos um limite rígido: nenhum pacote pode ultrapassar 64 KB (descompactado).

Verificação Automática (CI)

Temos um "fiscal" automático (scripts/check-size.sh) em nosso processo de desenvolvimento (CI - Integração Contínua). Se alguém tentar aprovar um projeto de pacote que gere uma carga maior que 64 KB, o fiscal barra a aprovação.

Por quê?

Para garantir que a regra seja cumprida e evitar problemas de performance antes mesmo que cheguem na autoestrada.

Limites em Diferentes Pontos

É importante saber que existem limites de tamanho em várias etapas (no "pedágio" do Broker, no "caminhão" do Producer, na "esteira" do Consumer), mas nosso principal foco é o tamanho original do pacote.

Camada Limite Importante O que significa?
Broker message.max.bytes Tamanho máximo de um pacote individual (descompactado)
Producer max.request.size Tamanho máximo do lote de pacotes enviado (compactado)
Consumer fetch.message.max.bytes Tamanho máximo da "leva" de pacotes que ele busca

Exemplo de Verificação de Tamanho

# Exemplo de como o fiscal verifica o tamanho
./scripts/check-size.sh \
  contracts/sales/order-created/v1/event.proto \ # O projeto do payload
  nstech.standard.sales.v1.OrderCreated \       # O tipo específico de payload
  contracts/sales/order-created/v1/sample.json \ # Um exemplo de dados
  65536                                          # O limite em bytes (64 * 1024)

Visualização de Tamanho

19.2 KB
0 KB 32 KB 64 KB

Desempacotando a Entrega: Lendo o Conteúdo (Desserialização)

Quando um pacote chega ao seu destino (Consumer), ele precisa ser aberto e seu conteúdo lido corretamente. Nossa abordagem simplifica isso:

Uma Faixa, Um Tipo de Carga

Cada tópico (faixa) na autoestrada transporta apenas um tipo específico de evento (ex: a faixa nstech.standard.sales.order-created.v1 SÓ transporta eventos OrderCreated na versão 1).

Por quê?

Isso torna o recebimento muito mais simples e seguro. O recebedor (Consumer) já sabe exatamente que tipo de pacote esperar naquela faixa, evitando confusão ou erros ao tentar abrir um pacote no formato errado. Chamamos isso de type-safe.

Recebedores Especializados (Handlers)

Para cada tipo de evento, temos um "especialista" (Handler) que sabe exatamente como processá-lo.

Como funciona o desempacotamento:

  1. Uma ferramenta padrão (ProtobufDeserializer<EventEnvelope>) abre a caixa de envio (o EventEnvelope).
  2. O especialista pega o conteúdo (payload binário) e usa o "manual" correto (OrderCreated.Parser) para decodificá-lo (ParseFrom), obtendo os dados de forma estruturada e segura.

Exemplo de Handler Especializado

// Exemplo de um especialista em "Pedido Criado"
public class OrderCreatedHandler : IEventHandler<OrderCreated> // Diz que sabe lidar com OrderCreated
{
    public Task HandleAsync(EventEnvelope envelope, OrderCreated payload, CancellationToken ct)
    {
        // Aqui vai a lógica: o que fazer quando um pedido é criado?
        // Ex: Atualizar estoque, notificar cliente, etc.
        Console.WriteLine($"Recebido Pedido: {payload.OrderId} do cliente {payload.CustomerId}");
        return Task.CompletedTask;
    }
}

// Como configuramos o "posto de recebimento" (Consumer)
builder.Services.AddKafkaConsumer(config =>
{
    // "Fique atento à faixa 'order-created.v1'..."
    config.Topic("nstech.standard.sales.order-created.v1")
          // "...e entregue os pacotes para o especialista OrderCreatedHandler."
          .WithHandler<OrderCreatedHandler>();
});
EventEnvelope
event_id: "abc-123"
timestamp: "2023-05-10T14:30:00Z"
version: "1.0.0"
source: "serviço-de-pedidos"
payload: [dados binários]
OrderCreated
orderId: "ORD-12345"
customerId: "CUST-789"
items: [...]
totalAmount: 157.50

Placas de Sinalização Claras: Padrões de Nomenclatura

Imagine dirigir em uma cidade onde as placas de rua são inconsistentes ou confusas. Seria um pesadelo! Por isso, temos padrões rígidos para dar nomes às nossas "faixas", "grupos de destino" e "formatos registrados".

Por quê?

Consistência! Facilita encontrar o que você precisa, entender rapidamente para que serve cada elemento e evita colisões de nomes.

O Quê Estamos Nomeando Formato Padrão Exemplo Concreto Explicação do Exemplo
Tópico (Faixa) org.service-layer.dominio.tipo-evento.vMAJOR nstech.core.foundation.user-created.v1 Evento order-created (pedido criado) do domínio sales (vendas) no Service Layer core da organização nstech, versão v1.
Consumer Group org.service-layer.proposito nstech.standard.analytics Um grupo de consumidores do Service Layer standard (org nstech) cujo propósito é analytics (análise de dados).
nstech.core.cache-warmup Outro grupo, para cache-warmup (aquecimento de cache).
Schema Subject (Nome no DETRAN) <nome-do-topico>-value nstech.standard.sales.order-created.v1-value O nome do registro do formato (schema) para o conteúdo (value) do tópico nstech.standard.sales.order-created.v1.

Seguir esses padrões não é opcional, é fundamental para a organização!

Documentação Técnica

Uma Viagem Completa: Exemplos do Dia a Dia

Vamos ver como um evento faz sua jornada completa na nossa autoestrada:

Enviando um Pacote (Publicação)

1

Ação no Sistema

O serviço-de-pedidos (Producer) detecta que um novo pedido foi finalizado.

2

Criação do Pacote

  • Ele cria os dados específicos do evento: OrderCreated (contendo ID do pedido, cliente, itens, etc.).
  • Ele "empacota" esses dados dentro do nosso EventEnvelope padrão, preenchendo event_id, timestamp, version ("1.0.0"), source ("serviço-de-pedidos"), etc. O OrderCreated vai no campo payload (em formato binário Protobuf).
3

Endereçamento

Ele define a key como o ID do pedido.

4

Compressão e Envio

Ele comprime o EventEnvelope completo usando zstd.

5

Postagem na Faixa Certa

Ele envia o pacote comprimido para a faixa (tópico) correta: nstech.standard.sales.order-created.v1.

Recebendo o Pacote (Consumo Estático/Simples)

1

Monitorando a Faixa

O serviço-de-notificacoes (Consumer) está configurado para ouvir a faixa nstech.standard.sales.order-created.v1 e usar o OrderCreatedHandler (nosso especialista).

2

Chegada do Pacote

Kafka entrega um lote de pacotes (mensagens) para o consumidor.

3

Desempacotamento

Para cada pacote:

  • O sistema primeiro decodifica o EventEnvelope usando o deserializador padrão.
  • Ele pega o payload (binário) do envelope.
  • Ele usa o OrderCreated.Parser (porque sabe que nesta faixa só vem OrderCreated v1) para decodificar o payload e obter o objeto OrderCreated com todos os dados do pedido.
4

Processamento

O OrderCreatedHandler recebe o envelope e o payload (OrderCreated) e executa sua lógica: talvez enviar um email de confirmação para o cliente.

Este fluxo garante que a informação certa chegue ao lugar certo de forma eficiente e segura.