Skip to content

About

Infnet Hub · TP4 do Projeto de Bloco (DR5, Infnet): microsserviços Spring Boot refatorados para arquitetura orientada a eventos com RabbitMQ: outbox, filas quorum, DLQ, saga e request/reply, com Keycloak (BFF), Next.js e Docker Compose.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Infnet Hub — TP4: Arquitetura Orientada a Eventos

Refatoração de um sistema acoplado para comunicação por eventos com RabbitMQ
quarta entrega do Projeto de Bloco: Engenharia de Softwares Escaláveis (DR5)

Relatório Técnico

Instituto Infnet Curso Disciplina

Testes API Cenários TLS Status License

Tela única de login do Infnet Hub



10 3 3 10 209 103
padrões de mensagem serviços de domínio, um banco cada nós no cluster RabbitMQ cenários de demonstração testes automatizados requisições na coleção HTTP

Índice

Entender Executar e verificar
Sobre o Projeto Como Executar
O acoplamento que esta entrega desfaz Demonstração
Arquitetura Testes Automatizados
Padrões de mensagem Testes de API
Garantias de entrega API REST
Autenticação Painel de operação
Segurança do transporte Estrutura do Projeto
Stack Relatório Técnico
Privacidade e dados (LGPD) Licença · Créditos

Sobre o Projeto

O Infnet Hub é uma plataforma educacional com feed de publicações, curtidas, comentários, vagas de carreira e desempenho acadêmico por competência. Cada entrega deu um passo:

flowchart LR
    TP1["TP1<br/>monólito em camadas"] --> TP2["TP2<br/>PostgreSQL"] --> TP3["TP3<br/>microsserviços<br/>Spring Cloud"] --> TP4["TP4<br/>eventos<br/>RabbitMQ"]
    style TP4 fill:#1e3a8a,stroke:#3b8ef5,color:#ffffff
Loading

Esta entrega troca a integração síncrona por eventos. O sistema deixa de perguntar e passa a anunciar: o que era uma chamada HTTP a um serviço que podia estar fora do ar virou um evento gravado numa tabela, entregue pelo RabbitMQ e aplicado pelo consumidor no próprio banco dele.

Important

A mudança que importa não é a troca de protocolo: o tempo deixou de ser requisito da integração. O nome de um aluno já está no banco do boletim desde que ele foi cadastrado, e chegou lá mesmo que o boletim estivesse desligado naquele momento.

Novidade O que é
📨 notificacao-service Serviço novo, que só consome: transforma eventos em notificações por pessoa e as entrega ao vivo por Server-Sent Events
🧩 Dez padrões de mensagem Cada um com caso de uso real, inclusive uma saga coreografada com compensação para remover uma pessoa dos três bancos (LGPD)
🛡️ Garantias de entrega Caixa de saída transacional, confirmação de publicação, consumidor idempotente e filas de mensagens mortas
🔐 Autenticação de verdade Keycloak com OIDC, PKCE e PAR, gateway no padrão Backend for Frontend; fim do cabeçalho X-Usuario-Id que qualquer um podia forjar
🐇 Broker em cluster Três nós do RabbitMQ com filas quorum replicadas: um nó cai e o sistema segue sem perder mensagem
🔒 Tráfego interno cifrado HTTPS, AMQPS e TLS no PostgreSQL e entre os nós do broker, com o certificado conferido em todos os trechos
📊 Painel de operação Prometheus e Grafana: filas, consumidores, caixa de saída, sagas e tempo de resposta

⬆ voltar ao índice

O acoplamento que esta entrega desfaz

Problema no TP3 Solução no TP4
boletim-service chamava infnethub-core por OpenFeign a cada consulta; com o core fora do ar, a tela mostrava "indisponível" Réplica local aluno_replica, alimentada por eventos. O Feign saiu do projeto
As notificações do front-end eram falsas, uma lista fixa em inbox.ts notificacao-service, alimentado por eventos, com entrega ao vivo
Excluir um usuário não se propagava: matrículas ficavam órfãs no outro banco, e quem tinha posts nem podia ser excluído (409) Saga de expurgo: o core bloqueia, o boletim anonimiza ou recusa, o core conclui ou desfaz o bloqueio e avisa quem pediu

Com o core desligado, o boletim continua respondendo com o nome do aluno:

Antes — TP3 Depois — TP4
Boletim indisponível no TP3 Boletim completo com o core fora do ar
Com o core fora do ar, a identificação do aluno ficava indisponível Com o core fora do ar, o nome vem da réplica local, posta lá por eventos

⬆ voltar ao índice

Arquitetura

flowchart LR
    FE[front-end<br/>Next.js] --> GW[api-gateway<br/>BFF + OAuth2]
    KC[(Keycloak)] -.OIDC.-> GW
    GW --> CORE[infnethub-core]
    GW --> BOL[boletim-service]
    GW --> NOT[notificacao-service]

    CORE -->|publica| MQ{{RabbitMQ<br/>3 nós}}
    MQ -->|"usuario.*"| BOL
    MQ -->|"usuario.* post.* vaga.*"| NOT
    MQ -->|comandos| NOT
    CORE <-->|request/reply| MQ
    MQ <-->|responde| BOL

    CORE --- DB1[(postgres<br/>core + outbox)]
    BOL --- DB2[(postgres<br/>boletim + réplica)]
    NOT --- DB3[(postgres<br/>notificação)]
    NOT -.SSE.-> FE

    style MQ fill:#ff6600,stroke:#b34700,color:#ffffff
    style NOT fill:#14532d,stroke:#22c55e,color:#ffffff
Loading

Note

Não há mais chamada direta entre serviços de negócio. A única ligação bidirecional é a consulta pontual que antecede uma exclusão, e ela também passa pelo broker.

Topologia no RabbitMQ
infnethub.eventos      topic     usuario.* post.* vaga.*   → uma fila por consumidor
infnethub.comandos     direct    notificacao.enviar        → fila de trabalho
                                 notificacao.agendar       → sala de espera (TTL → DLX)
infnethub.consultas    direct    boletim.situacao          → fila com validade de 10 s
infnethub.dlx          direct    <fila>.dlq                → mensagens que esgotaram tentativas
infnethub.notificacoes.aovivo   fanout                     → uma fila exclusiva por instância

Filas nomeadas por consumidor, não por evento: boletim.usuarios e notificacao.eventos recebem os mesmos eventos de usuário, cada uma no seu ritmo, com sua própria fila de mensagens mortas.

Portas

Só o que um navegador abre é publicado, e só no 127.0.0.1 da máquina:

Para Host
Front-end 20000
API Gateway 20080
Keycloak (login) 20180
Grafana (painel de operação) 20300
RabbitMQ (painel de gestão) 20673
PostgreSQL / AMQP, para rodar um serviço pela IDE 20432–20434 / 20672

Os serviços Java e o Eureka não têm porta no host: chega-se a eles pelo gateway, e só por ele.

⬆ voltar ao índice

Padrões de mensagem

Padrão Mecanismo Caso de uso
Publish/Subscribe exchange topic usuario.cadastrado alimenta a réplica do boletim e gera as boas-vindas
Event-Carried State Transfer payload com o estado UsuarioAtualizado leva nome, escola e papel: o boletim não pergunta de volta
Event Notification payload com ids PostCurtido leva o mínimo; quem quiser o post busca o post
Comando ponto a ponto exchange direct EnviarAviso: a secretaria fala com a turma
Competing Consumers fila compartilhada --scale notificacao=3 → 9 consumidores na mesma fila
Fanout exchange fanout a notificação chega à instância que tem a conexão SSE aberta
Retry + Dead Letter x-delivery-limit + DLX mensagem venenosa sai de circulação e fica guardada
Mensagem com atraso cascata de 22 níveis com TTL de 2ⁿ s "avise quando faltarem 24 h para o prazo", sem bloqueio de cabeça de fila
Request/Reply reply-to + correlation-id situação acadêmica antes de excluir um cadastro
Saga coreografada eventos nos dois sentidos + estado no core remoção (LGPD): bloqueia → anonimiza ou recusa → conclui ou compensa

Evento, comando e consulta são três coisas diferentes, e o módulo contratos diz isso no sistema de tipos:

Evento Comando Consulta
diz aconteceu isto faça isto me diga isto
destinatários nenhum, ou muitos exatamente um um, que responde
pode ser recusado não: fato é fato sim sim
passa pela caixa de saída sim sim não

Tip

A última linha é a que mais importa: uma pergunta cuja resposta chegue tarde não vale nada, então não recebe as garantias caras que um evento recebe.

⬆ voltar ao índice

Garantias de entrega

O que separa "usar RabbitMQ" de "usar RabbitMQ bem":

Garantia Como
📥 Caixa de saída transacional O evento é gravado na mesma transação do agregado; um relay entrega depois. Publicar direto teria uma falha em cada ordem
✅ Confirmação de publicação com mandatory Sem fila ligada à chave, o broker devolve em vez de descartar, e o evento continua pendente
🔁 Consumidor idempotente mensagem_processada com o messageId como chave primária, gravada na mesma transação do efeito
🧱 Filas quorum com limite de entregas x-delivery-limit: 5 impede que uma mensagem defeituosa derrube o consumidor em laço
🔢 Ordem por agregado, versão no payload Um evento atrasado de um usuário é descartado pelo consumidor, sem reter os dos outros

Com o broker inteiro fora do ar, o relay registrou as tentativas, não perdeu nada e publicou sozinho quando o broker voltou:

tentativas: 20
último erro: broker inacessível: java.net.UnknownHostException: rabbitmq

⬆ voltar ao índice

Autenticação

Até o TP3 a identidade era o cabeçalho X-Usuario-Id, aceito sem verificação. A correção é Keycloak + BFF, e não um JWT entregue ao navegador, porque um token em localStorage é legível por qualquer script da página.

sequenceDiagram
    participant N as Navegador
    participant G as api-gateway (BFF)
    participant K as Keycloak
    G->>K: pedido de login por trás (PAR + PKCE)
    K-->>G: referência de uso único
    G-->>N: redireciona só com client_id e a referência
    N->>K: usuário e senha
    K-->>G: código → tokens
    G-->>N: cookie SESSION (HttpOnly)
    N->>G: chamadas com o cookie
    G->>G: TokenRelay: o token segue para os serviços
Loading
  • o navegador recebe só um cookie HttpOnly, que o JavaScript não lê;
  • cada serviço valida o token por conta própria: confiar em "veio do gateway" é confiar na rede;
  • quem não é navegador (scripts, a coleção HTTP) usa Authorization: Bearer.

Note

Contas de demonstração, todas com a senha infnet:

Usuário Papel Pode
lucas.mendonca ALUNO ver o próprio boletim, publicar no feed
carlos.oliveira PROFESSOR ver o boletim de qualquer aluno, ler a auditoria, avisar a turma
atendimento SECRETARIA publicar vagas, administrar o cadastro, avisar a turma
suporte.ti SUPORTE_TI entrar no painel de operação (Grafana); nenhum outro papel entra

⬆ voltar ao índice

Como Executar

docker compose up -d

Sobe dezesseis contêineres na ordem certa, com depends_on: service_healthy: o gerador de certificados, Keycloak, três nós do RabbitMQ, três PostgreSQL, Eureka, os três serviços, gateway, front-end, Prometheus e Grafana.

Para ver Onde
🖥️ Aplicação http://localhost:20000
📊 Painel de operação http://localhost:20300 (Grafana, só suporte.ti)
🐇 Painel do RabbitMQ http://localhost:20673 (infnethub / infnethub)
🔑 Keycloak http://localhost:20180

Para derrubar tudo: docker compose down.

Warning

O ambiente do Compose é de desenvolvimento: senhas conhecidas, realm importado de arquivo e autoridade certificadora local. Uma implantação real troca esses três itens de configuração, sem mudar o código.

Rodando um serviço pela IDE

O broker só aceita AMQP por TLS. Copie a autoridade de desenvolvimento para fora do Docker:

./infra/certificados/exportar.sh

Na configuração de execução do serviço, acrescente a variável SPRING_RABBITMQ_SSL_ENABLED=true e as opções de JVM -Djavax.net.ssl.trustStore=target/certificados/confianca.p12 -Djavax.net.ssl.trustStorePassword=infnethub-confianca -Djavax.net.ssl.trustStoreType=PKCS12. Os bancos e o AMQP do primeiro nó estão no 127.0.0.1 para isso.

⬆ voltar ao índice

Segurança do transporte

Toda conexão entre contêineres é cifrada, e em todas o certificado é conferido:

Trecho Como
serviços → PostgreSQL TLS com sslmode=verify-full; o banco recusa conexão sem TLS
serviços → RabbitMQ AMQPS (TLS 1.3) na 5671; a porta sem cifragem é desligada
nó ↔ nó do RabbitMQ distribuição Erlang por TLS, com verificação mútua
gateway → serviços; serviços → Eureka e Keycloak; front-end → gateway HTTPS
Prometheus → alvos; Grafana → Prometheus HTTPS

A autoridade é de cada ambiente: gerada na primeira subida por infra/certificados/gerar.sh, emite um único certificado e tem a chave apagada em seguida.

⬆ voltar ao índice

Painel de operação

O Grafana pede login pelo Keycloak e só aceita o papel SUPORTE_TI. O painel Infnet Hub — operação mostra nós do broker, mensagens paradas e mortas, caixa de saída de cada serviço, sagas abertas, filas e consumidores, avisos na sala de espera, taxas de publicação e tempo de resposta.

Painel de operação no Grafana

⬆ voltar ao índice

Demonstração

Dez cenários reproduzíveis. Nenhum é simulação: os contêineres são derrubados de verdade, e o que o roteiro mostra é o que o RabbitMQ e os bancos respondem.

./demo/cenarios.sh          # lista os cenários
./demo/cenarios.sh 2        # roda um
./demo/cenarios.sh todos    # roda os dez

Tip

No Windows, rode pelo PowerShell chamando o Bash do Git pelo caminho completo (o bash do PATH pode ser o do WSL, que não enxerga as ferramentas do Windows). Há também demo/cenarios.ps1.

& "$env:USERPROFILE\scoop\apps\git\current\bin\bash.exe" ./demo/cenarios.sh 1
# Cenário O que prova
1 O consumidor pode estar fora do ar a fila acumula e é aplicada quando ele volta
2 O boletim funciona sem o core o acoplamento do TP3, desfeito
3 O broker pode estar fora do ar caixa de saída transacional
4 A mensagem que não dá para processar recusa e fila de mensagens mortas
5 A mesma mensagem duas vezes consumidor idempotente
6 Três instâncias dividindo trabalho competing consumers e fanout
7 Pedido para agora e para depois atraso em cascata: um aviso de 5 s chega em 5 s, mesmo atrás de um de 1 hora
8 A pergunta que espera resposta request/reply, a exceção síncrona
9 Remover uma pessoa de três bancos saga: conclusão, recusa com compensação e prazo vencido
10 Um nó do broker cai cluster de três nós: novo líder na hora, eventos seguem

A captura da execução de cada cenário está no relatório, na seção 24. Algumas telas:

O sino alimentado por eventos A saga de expurgo, nos três desfechos
Feed com notificações Cenário 9
Cluster de três nós Um nó fora, o sistema segue
Cluster RabbitMQ Nó fora do ar

⬆ voltar ao índice

Testes Automatizados

209 testes, todos passando:

./mvnw test
Módulo contratos infnethub-core boletim-service notificacao-service api-gateway eureka-server
Testes 24 68 68 24 23 2

Os testes de mensageria usam Testcontainers com RabbitMQContainer e @ServiceConnection: um broker de verdade, não um simulador. É o único jeito honesto de provar confirmação de publicação, devolução por falta de fila, expiração com dead letter e limite de entregas.

⬆ voltar ao índice

Testes de API

103 requisições em treze arquivos, executadas pelo HTTP Client da JetBrains em contêiner, com as asserções de cada bloco rodando sem alteração:

./verificar.sh gateway
98 requests completed, 0 have failed tests
Aguardando a saga de expurgo dar a volta…
9 requests completed, 0 have failed tests
Coleção HTTP: OK — relatório JUnit em target/http-reports
O que cada arquivo cobre
Arquivo Cobre
00-autenticacao.http um token por papel, e a recusa de credencial inválida
01–05 usuários, posts, comentários, vagas, histórico
06-erros.http 400, 404, 401 sem token, 401 com token inválido, 403 por papel
07-boletim.http boletim, desempenho e a regra de acesso por pessoa
08-notificacoes.http leitura, contagem, baixa e a data com fuso
09-avisos.http o comando: 403 de aluno, 202, alcance e agendamento
10-consulta-boletim.http o request/reply, e a entrega atrasada que não chegou
11-expurgo.http a saga aberta: 202 em SOLICITADO, 409 no pedido repetido, o bloqueio em todas as leituras
12-expurgo-desfecho.http o desfecho: REVERTIDO com motivo, a pessoa de volta e o aviso para quem pediu

A saga leva perto de um segundo para dar a volta, e o cliente HTTP não sabe esperar: por isso são duas chamadas, com a pausa explícita no script.

⬆ voltar ao índice

API REST

Tudo atrás do gateway, em http://localhost:20080. Nenhuma rota responde sem token.

Rotas novas no TP4
Método Rota Papel Descrição
GET /api/v1/notificacoes autenticado as notificações de quem pede
GET /api/v1/notificacoes/nao-lidas autenticado o número do sino
GET /api/v1/notificacoes/ao-vivo autenticado fluxo SSE
POST /api/v1/notificacoes/lidas autenticado marca as escolhidas como lidas ({"ids":[…]})
POST /api/v1/notificacoes/nao-lidas autenticado devolve o destaque às escolhidas
POST /api/v1/notificacoes/lidas/todas autenticado baixa geral
POST /api/v1/notificacoes/excluir autenticado exclusão em lote ({"ids":[…]})
POST /api/v1/avisos professor, secretaria envia um aviso; aceita atrasoSegundos · responde 202
GET /api/v1/usuarios/{id}/situacao-academica professor, secretaria pergunta ao boletim pelo broker
POST /api/v1/usuarios/eventos/reenvio secretaria republica o estado atual, para a carga de um consumidor novo
DELETE /api/v1/usuarios/{id} secretaria, coordenação mudou: abre a saga de expurgo · responde 202
GET /api/v1/usuarios/{id}/expurgo secretaria, coordenação histórico dos processos de expurgo

202, e não 201, no aviso: não nasce recurso com endereço próprio, nasce um pedido aceito. 202, e não 204, na remoção: o que acontece nos outros serviços ainda está em curso. Um segundo pedido com a saga em curso responde 409.

Contrato de erro
{
  "timestamp": "2026-09-22T22:28:57.842",
  "status": 400,
  "error": "Corpo da requisição inválido",
  "message": "Não foi possível interpretar o corpo enviado."
}

⬆ voltar ao índice

Estrutura do Projeto

Árvore de pastas
TP4-PB_eventos/
├── contratos/                  os contratos das mensagens — a linguagem comum
│   └── …/contratos/
│       ├── MensagemDeIntegracao.java      metadados comuns
│       ├── EventoDeIntegracao.java        fato anunciado
│       ├── comando/                       pedido endereçado
│       ├── consulta/                      pergunta e resposta
│       ├── usuario/ feed/ vaga/           os contratos versionados
│       └── Canais.java                    exchanges, rotas e filas
├── infnethub-core/             identidade, feed, vagas · PRODUTOR
│   └── …/mensageria/
│       ├── CaixaDeSaida.java              grava na transação do agregado
│       ├── RelayDoOutbox.java             publica, espera o ack, marca
│       └── ConsultaAoBoletim.java         request/reply, com prazo
│   └── …/expurgo/SagaDeExpurgo.java       a saga: bloqueia, conclui ou compensa
├── boletim-service/            desempenho acadêmico · CONSUMIDOR
│   └── …/aluno/                           réplica local + participação na saga
├── notificacao-service/        NOVO no TP4 · só consome
│   ├── …/mensageria/                      eventos e comandos, em filas separadas
│   └── …/aovivo/                          fanout entre instâncias + SSE
├── api-gateway/                roteamento + BFF (OIDC, PAR, sessão, TokenRelay)
├── eureka-server/              descoberta
├── frontend/                   Next.js
├── infra/
│   ├── rabbitmq/                          topologia, cluster de três nós e TLS
│   ├── keycloak/                          realm e o tema da tela de login
│   ├── certificados/                      autoridade de desenvolvimento (gerada, não versionada)
│   ├── postgres/pg_hba.conf               conexão pela rede só com TLS
│   ├── prometheus/                        o que coletar, e de onde
│   └── grafana/                           painel de operação provisionado
├── demo/cenarios.sh · .ps1     dez cenários reproduzíveis
├── http/                       coleção de 103 requisições
├── doc/
│   ├── RELATORIO_TP4-PB.md                relatório técnico
│   ├── asyncapi.yaml                      especificação dos canais
│   └── screenshots/                       capturas da demonstração
└── docker-compose.yml

⬆ voltar ao índice

Stack

Camada Tecnologias
Back-end Java Spring Boot Spring Cloud Maven
Mensageria RabbitMQ Spring AMQP AsyncAPI
Dados PostgreSQL Flyway Hibernate
Segurança Keycloak OpenID TLS
Front-end Next.js React TypeScript
Observabilidade Prometheus Grafana Micrometer
Testes JUnit Testcontainers JetBrains
Infra Docker Eureka Gateway

⬆ voltar ao índice

Relatório Técnico

Relatório Técnico AsyncAPI

A documentação completa está em doc/RELATORIO_TP4-PB.md, em seis partes:

Parte Conteúdo
I Contexto e avaliação: prós e contras medidos neste sistema, e onde eventos não valem
II Padrões de mensagem: catálogo, topologia e a sequência de cada fluxo
III Garantias de entrega: outbox, confirmações, idempotência, quorum e DLQ
IV Abstrações do Spring Boot e as decisões de ferramenta
V A refatoração: Feign → réplica, notificações reais, autenticação
VI Qualidade e operação: testes, demonstração, armadilhas e cobertura das rúbricas

A especificação dos canais está em doc/asyncapi.yaml (AsyncAPI 3.0).

⬆ voltar ao índice

Licença

Licença de Código Visível — Somente Leitura. Todos os direitos reservados. Este repositório é source-available, não código aberto: é público para que o trabalho possa ser lido, estudado e avaliado.

Permissão
✅ Livre Ler e analisar o código nesta página; citar trechos curtos com atribuição
🎓 Avaliação acadêmica O corpo docente e a coordenação da instituição podem obter e executar o projeto no necessário à correção
⛔ Requer autorização escrita Copiar, clonar ou executar · modificar · redistribuir ou hospedar · uso comercial · treinar modelos de IA · apresentar como trabalho acadêmico próprio

Caution

Isso inclui clonar e rodar localmente. Um fork permitido pelos Termos do GitHub não amplia a concessão de leitura nem constitui autorização. Pedir autorização é gratuito e costuma ser respondido: abra uma issue no repositório. Ausência de resposta não equivale a autorização.

O texto integral, com as definições, a cláusula de integridade acadêmica e o aviso de proteção de dados (LGPD), está em LICENSE.

⬆ voltar ao índice

Privacidade e dados (LGPD)

O repositório não contém dados pessoais reais: os registros semeados e os usuários do realm do Keycloak são fictícios. A identidade deixou de ser um cabeçalho forjável, o acesso ao boletim é verificado por pessoa e a trilha de auditoria registra a identidade do token.

O que a arquitetura de eventos acrescentou, e como cada ponto foi tratado
  • Dado pessoal replicado. Nome, escola e papel do aluno existem em dois bancos, o do core e a aluno_replica do boletim. A eliminação alcança os dois pela saga de expurgo.
  • A eliminação é uma saga, e não um DELETE. O core bloqueia o cadastro e anuncia o pedido; o boletim apaga a réplica (as matrículas ficam, sem identificação) ou recusa, se houver competência em avaliação; o core então anonimiza o cadastro (nome "Usuário removido", e-mail removido+{id}@infnethub.invalid) ou desfaz o bloqueio e avisa quem pediu. Se o boletim não responder, o prazo desfaz o bloqueio; se responder depois, o core o faz restaurar o que apagou. O processo fica registrado em expurgo_lgpd.
  • A trilha de auditoria também é anonimizada, na mesma transação que conclui a saga: continua mostrando o que aconteceu e em que ordem, sem identificar ninguém.
  • Dado pessoal em filas. UsuarioRemovidoV1 e o pedido de expurgo levam só o id. As filas de mensagens mortas descartam o conteúdo em sete dias (política do broker).
  • Aviso de cookies. Consentimento por categoria (essenciais, preferências, estado das janelas), com o mesmo peso para aceitar e rejeitar, e a opção de apagar os dados locais.
  • Trânsito cifrado. Toda conexão entre contêineres é TLS, com o certificado conferido.

⬆ voltar ao índice

Créditos

Asset Autor Licença
Ilustrações 3D da página 404 (antena, satélite e astronauta) Vecteezy Vecteezy License, atribuição exigida

As ilustrações foram convertidas para WebP e ajustadas em cor e recorte. Os originais não são versionados: somam cerca de 59 MB e nenhum é carregado em execução.

⬆ voltar ao índice

LinkedIn GitHub

Infnet Hub — Projeto de Bloco, Instituto Infnet

About

Infnet Hub · TP4 do Projeto de Bloco (DR5, Infnet): microsserviços Spring Boot refatorados para arquitetura orientada a eventos com RabbitMQ: outbox, filas quorum, DLQ, saga e request/reply, com Keycloak (BFF), Next.js e Docker Compose.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages