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écnicaAntes de pegar a estrada, vamos conhecer alguns termos chave do nosso sistema de transporte:
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.
Para que nossa autoestrada funcione bem para todos, temos algumas regras de ouro:
Mantenha seus eventos pequenos (idealmente até 64 KB).
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.
Atualizações pequenas (MINOR/PATCH no SemVer) nunca devem "quebrar" quem já está na estrada.
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.
Tudo fica registrado e não pode ser alterado depois de enviado.
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.
Cada tipo de evento (pacote) vem com seu próprio manual.
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.
Todos os veículos e postos de serviço têm painéis de controle visíveis.
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.
Agora, vamos ver como construir nossas "mensagens" (eventos) da maneira correta.
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.
// 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
}
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)
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.
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.
Sempre que enviamos um pacote (produzimos um evento), usamos compressão (zstd).
É 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.
Já falamos que pacotes pequenos são melhores. Temos um limite rígido: nenhum pacote pode ultrapassar 64 KB (descompactado).
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.
Para garantir que a regra seja cumprida e evitar problemas de performance antes mesmo que cheguem na autoestrada.
É 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 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)
Quando um pacote chega ao seu destino (Consumer), ele precisa ser aberto e seu conteúdo lido corretamente. Nossa abordagem simplifica isso:
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).
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.
Para cada tipo de evento, temos um "especialista" (Handler) que sabe exatamente como processá-lo.
ProtobufDeserializer<EventEnvelope>) abre a caixa de envio (o EventEnvelope).payload binário) e usa o "manual" correto (OrderCreated.Parser) para decodificá-lo (ParseFrom), obtendo os dados de forma estruturada e segura.// 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>();
});
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".
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!
Vamos ver como um evento faz sua jornada completa na nossa autoestrada:
O serviço-de-pedidos (Producer) detecta que um novo pedido foi finalizado.
OrderCreated (contendo ID do pedido, cliente, itens, etc.).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).Ele define a key como o ID do pedido.
Ele comprime o EventEnvelope completo usando zstd.
Ele envia o pacote comprimido para a faixa (tópico) correta: nstech.standard.sales.order-created.v1.
O serviço-de-notificacoes (Consumer) está configurado para ouvir a faixa nstech.standard.sales.order-created.v1 e usar o OrderCreatedHandler (nosso especialista).
Kafka entrega um lote de pacotes (mensagens) para o consumidor.
Para cada pacote:
EventEnvelope usando o deserializador padrão.payload (binário) do envelope.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.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.