sexta-feira, 26 de dezembro de 2025

KAFKA - Principais conceitos

        Nesse artigo vou trazer um resumo sobre o Kafka. Sempre gosto de fazer resumos que podem me ajudar no dia a dia, afinal, é impossível lembrar de tudo o tempo todo.


- Fila, Tópico e Grupos


Apresenta o conceito de fila ou tópico (Pub/Sub):

Kafka -> fila -> Só tem um grupo, o primeiro nó do grupo que ler trata a mensagem.

        '--> tópico -> a mesma mensagem pra vários grupos, onde em cada grupo o primeiro nó que pegar lê e trata a mensagem (Pub/Sub).


Vamos ver o coneito de grupo no exemplo abaixo:


@KafkaListener(

    topics = "pedidos",

    groupId = "pagamento-service"

)

public void consumir(Pedido pedido) {

}


        Se o microsserviço A é do grupo "pagamento-service" e apenas esse grupo acessa o tópico "pedidos", é uma fila. Pode ter várias instancias de A que não impacta nesse comportamento de fila.


        Se crio o microsserviço B e ele tem a seguinte definição de grupo: groupId = "estoque-service"


@KafkaListener(

    topics = "pedidos",

    groupId = "estoque-service"

)

public void consumir(Pedido pedido) {

}


        Então agora temos o comportamento de Pub/Sub (tópico), porque o grupo "estoque-service" também acessa "pedido".


*Note que há um overhead de conceitos aí na questão de tópico, uma coisa é o tópic do Kafka e outra é o conceito de tópico no sentido de Pub/Sub, onde alguém publica e outros são (sub)inscritos para receber a mensagem.


Usando mensagens diferentes no mesmo tópico


        Embora pareça estranho, usar mensagens diferentes no mesmo tópico não é errado. É assim por exemplo que se garante a ordem de execução, usando a mesma key (vamos ver esse coneito mais a frente) e tópico o que joga na mesma partição (vamos ver esse coneito mais a frente), mas o corpo da mensagem é diferente, e cada consumer que tem seu group-id consome o que lhe interessa e descarta o resto. Ignorar evento NÃO é erro.


Exemplo: Podemos ter o Tópico: pedidos e usar a key = pedidoId


Eventos (Cada evento é uma mensagem diferente, com campos diferente):

- PEDIDO_CRIADO

- PEDIDO_PAGO

- PEDIDO_ENVIADO

- PEDIDO_CANCELADO


        Isso casa perfeitamente com Event Sourcing, DDD, CQRS, Auditoria, Reprocessamento...


        Por que usar o MESMO tópico nesse caso? Porque você quer garantir:


✔️ Ordem dos eventos do pedido

✔️ Processamento sequencial

✔️ Consistência de estado

✔️ Um único consumer por pedido

✔️ Reprocessamento confiável



- Partições:


        Partições -> paralelismo/escalabilidade. São divisões dentro de um tópico. Cada partição permite apenas um consumidor por grupo. Se tiver mais consumidores que partições de um mesmo grupo eles ficam ociosos.


        Partição NÃO é algo que você “escala dinamicamente” como pod de Kubernetes. Partição é uma decisão estrutural, onde alterar depois tem efeitos colaterais


        Kafka foi desenhado para escalar consumidores, mas com partições pensadas antes. Ao alterar partições, as partições novas começam vazias, as chaves (keys) podem ir para partições diferentes, a ordem global não é preservada, pode quebrar a lógica baseada em key.


👉 Por isso: erramos para mais, não para menos. Partições devem acompanhar o pico de consumo esperado, não o consumo médio.


- Keys


        As Keys servem para decidir em qual partição a mensagem vai cair. Mesma key significa que a mensagem sempre vai cair na mesma partição. Isso é importante porque o Kafka só garante ordem dentro da partição.


Quando usar key:


✔️ Existe uma entidade de negócio

✔️ Eventos precisam ser processados em ordem

✔️ Existe estado

✔️ Existe atualização incremental


Quando NÃO usar key:


❌ Eventos são independentes

❌ Não existe estado

❌ Ordem não importa

❌ Quer máximo throughput


Keys influenciam na escalabilidade (trade-off real)


Quanto mais granular a key:

  • Mais espalhamento
  • Mais paralelismo
  • Menos ordem global


Quanto mais concentrada a key:


  • Menos paralelismo
  • Mais ordem
  • Possível gargalo


Exemplo de chave ruim: key = "PEDIDOS" -> Tudo cai em uma partição só e o Kafka vira single-thread.


Exemplo de chave boa: pedidoId, userId, contaId, CPF, CNPJ...


        Quando se trabalha com Keys e é necessário aumentar partições, a mesma key pode ir para partições diferentes no momento dessa alteração e a ordem não será garantida. Isso acontece porque o calculo de pra qual partição a mensagem vai é: "hash(key) % N" onde N é o número de partições. Por isso sistemas que dependem de key precisam pensar bem antes de aumentar partições. É preciso fazer uma análise dos riscos.


Comando para criar um tópico em Kafka:

./kafka-topics.sh --create --topic <nome_do_topico> --bootstrap-server <endereco_do_broker> --partitions <numero_de_particoes> --replication-factor <fator_de_replicacao>








quinta-feira, 18 de dezembro de 2025

API first - ótimo com IA

        API first é uma abordagem de desenvolvimento de software onde o contrato de definição da API é feito primeiro que o código.

        Aplicar o "API first" sem IA é um trabalho chato e acho que demora mais do que sentar na reunião e fazer algumas anotações sobre os recursos e suas interações na mão, depois codar a API e disponibilizar para a outra equipe (se for o caso) e fazer alguma correção. Achei realmente muito cansativo, trabalhoso e demorado. Em uma empresa que trabalhei, para APIs externas era usado esse conceito, mas não era o time de dev do projeto (vulgo squad) que fazia, era uma equipe só pra isso, tinha que abrir chamado e passar o JSON do recurso, os endpoints e tal (meio como vou fazer abaixo com o prompt da IA).

        O motivo principal de fazerem isso na empresa para APIs externas ao invés de simplesmente configurar o Swagger no projeto com Spring Boot e ele ir tomando forma acredito era pra disponibilizar o Swagger para clientes externos, uma vez que o Swagger do projeto não ficaria exposto em produção, então eles tinha um endereço de documentação onde expunham o .yaml gerado para clientes externos acessarem. Pra equipes internas não sei se valeria o trabalho. 

        Com a IA é molesa, mas na mão, vi em um vídeo (que vou deixar no final) e achei um inferno! Então vou deixar o prompt aqui de exemplo para usar sempre que for preciso. Claro que no momento de gerar pode (e deve) passar mais informações, como campos obrigatórios, se usa autenticação, qual tipo de autenticação, url de autenticação, tudo isso é importante para a documentação da API!

Cria pra mim o .yaml do swagger para API first seguindo a seguinte especificação:

openapi: 3.1.0

title: pedidos
description: DOC API pedidos

servers: https://localhost:8080/pedidos-api

versione no padrão: /api/v1/recurso

Recurso /pedidos

GET - 200 OK

GET {id} - 200 OK ou 404 Not Found

POST - 201 Created ou 400 Bad Request

PUT {id} - 202 Accepted ou 404 Not Found

DELETE {id} - 202 Accepted ou 404 Not Found

JSON objeto:
 
{
  "idPedido": 160253,
  "idCliente": 123658,
  "produtos": [{
        "idProduto": 6548,
        "descricao": "Livro Java - Como programar",
        "quantidade": 1,
        "valorUnitario": 250.00
  }]
  
}

O link do vídeo que ensina a fazer a documentação na mão é o: https://www.youtube.com/watch?v=cksLbT8nDB4

segunda-feira, 8 de dezembro de 2025

JPA - Anotações de relacionamento e dicas úteis

        Nesse artigo vou abrodar as anotações de relacionamento do JPA trazendo algumas dicas úteis, principalmente no @ManyToMany:


 1. @OneToOne


O que é: Relacionamento um-para-um → um registro se relaciona com exatamente um outro registro, não podendo pertencer a mais de um.


- Pode ser unidirecional ou bidirecional

- Bidirecional exige 'mappedBy'.


Exemplo (unidirecional) Usuário ↔ Endereço (apenas se for 1 único endereço por pessoa)


@Entity

class Pessoa {

    @Id

    private Long id;


    @OneToOne

    private Endereco endereco;

}


Exemplo (bidirecional)


@Entity

class Pessoa {

    @Id

    private Long id;


    @OneToOne(mappedBy = "pessoa")

    private Endereco endereco;

}


@Entity

class Endereco {

    @Id

    private Long id;


    @OneToOne

    @JoinColumn(name = "pessoa_id")

    private Pessoa pessoa;

}


2. @OneToMany


O que é: Um objeto possui muitos do outro lado.

Ex.: Um cliente tem muitos pedidos.


- É bidirecional, com o lado dono sendo o '@ManyToOne'.


Exemplo (bidirecional)


@Entity

class Cliente {

    @Id

    private Long id;


    @OneToMany(mappedBy = "cliente")

    private List<Pedido> pedidos;

}


@Entity

class Pedido {

    @Id

    private Long id;


    @ManyToOne

    @JoinColumn(name = "cliente_id")

    private Cliente cliente;

}


3. @ManyToOne


O que é: Muitos objetos se relacionam com um do outro lado.


- Pode ser unidirecional, mas geralmente faz parte de um relacionamento bidirecional com '@OneToMany'.


Exemplo


@Entity

class Pedido {

    @Id

    private Long id;


    @ManyToOne

    @JoinColumn(name = "cliente_id")

    private Cliente cliente;

}


4. @ManyToMany


O que é: Muitos para muitos.

Ex.: alunos x cursos.


- Pode ser unidirecional ou bidirecional.

- Normalmente bidirecional, e JPA cria tabela intermediária.

- Pode haver casos de precisar manusear o objeto intermediário, então não use ManyToMany, modele as relações como: Na tabela intermediária -> @ManyToOne para cada lado; Nos lados -> @OneToMany para a entidade intermediária (dependendo da navegação). Vou mostrar um exemplo mais abaixo. Isso transforma o N:N em duas relações 1:N + N:1.


Exemplo @ManyToMany (bidirecional)


@Entity

class Aluno {

    @Id

    private Long id;


    @ManyToMany(mappedBy = "alunos")

    private List<Curso> cursos;

}


@Entity

class Curso {

    @Id

    private Long id;


    @ManyToMany

    @JoinTable(

        name = "aluno_curso",

        joinColumns = @JoinColumn(name = "curso_id"),

        inverseJoinColumns = @JoinColumn(name = "aluno_id")

    )

    private List<Aluno> alunos;

}


        Agora vamos ao exemplo onde precisamos trabalhar com o objeto intermediário, no caso, a tabela aluno_curso.

        Precisamos adicionar campos a ela, colocar notas dos alunos, data da matricula, etc.Passamos a ter a Entidade 

        AlunoCurso e agora tando Aluno quanto Curso apontam pra ela com @OneToMany e ela aponta pra ambos com @ManyToOne:


@Entity

public class Aluno {

    @Id

    private Long id;


    private String nome;


    @OneToMany(mappedBy = "aluno", cascade = CascadeType.ALL)

    private List<AlunoCurso> cursos = new ArrayList<>();

}


@Entity

public class Curso {


    @Id

    private Long id;


    private String nome;


    @OneToMany(mappedBy = "curso", cascade = CascadeType.ALL)

    private List<AlunoCurso> alunos = new ArrayList<>();

}


@Entity

public class AlunoCurso {


    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;


    @ManyToOne

    @JoinColumn(name = "aluno_id")

    private Aluno aluno;


    @ManyToOne

    @JoinColumn(name = "curso_id")

    private Curso curso;


    private LocalDate dataMatricula;

    private List<Double> notas;

}


Dicas importantes:

- Só faça carregamento bidirecionais se for realmente preciso.

- Sempre prefira LAZY nos @ManyToOne, evita carregar todo o curso ao carregar um aluno.

- Evite usar chave composta quando possível, ID próprio simplifica a vida.

- Os lados “Aluno” e “Curso” podem ser unidirecionais, você não é obrigado a colocar o @OneToMany neles se não precisar navegar, quem sabe usar um @Transient e carregar somente quando necessário. Por exemplo, você pode deixar o lado do Curso unidirecional, removendo o @OneToMany e colocando o @Transient apenas para uso em memória. Importante: O Curso continua relacionado à tabela intermediária (AlunoCurso) — você só está escolhendo não mapear a navegação inversa no JPA. Isso melhora performance, reduz carga de memória e evita N+1.


@Entity

public class Curso {

    @Id

    private Long id;


    private String nome;


    @Transient

    private List<AlunoCurso> alunosCurso; // carregado manualmente quando e onde precisar

}


        Se quiser pode remover completamente ou mapear apenas alunos diretamente (de forma transiente) carregando manualmente também quando precisar. Esses detalhes variam de acordo com o negócio.