- Jose Ricardo @JOSER1CARDO
- Leonardo Silva - @Leonardo-Da-Silva-Rocha
- Douglas dos Santos - @Douglasdossantos
- Geraldo Alves Simão Junior - @geraldsimon
- Silvio Kinaake - @Silviokinaake
- Diego Lobo - @diegolobo
- Alberto - @nauthin
Este repositório contém a entrega do Módulo 5 do MBA DevXpert Full Stack .NET, que propõe a evolução de uma aplicação monolítica para uma plataforma educacional distribuída baseada em microsserviços, bounded contexts bem definidos, comunicação síncrona via HTTP e assíncrona via broker.
A solução modela o ciclo de vida completo de um aluno em uma plataforma de cursos online: cadastro, matrícula, pagamento, acompanhamento de progresso em aulas e conclusão do curso. Cada responsabilidade vive em um contexto isolado (Auth, Aluno, Conteúdo, Pagamentos) e um BFF (Backend for Frontend) orquestra a experiência do usuário final.
A comunicação entre os contextos ocorre de duas formas complementares. As operações síncronas (consulta de dados entre contextos e orquestração no BFF) são feitas via HTTP, idealmente com IHttpClientFactory + Refit + políticas de resiliência Polly (retry exponencial + circuit breaker). As operações assíncronas de integração (ex.: confirmação de pagamento acionando a ativação de uma matrícula) são publicadas em RabbitMQ através do abstrator IMessageBus (EasyNetQ), permitindo que cada serviço evolua de forma independente sem acoplamento direto entre APIs.
Publicado e automatizado: a plataforma está no ar em três ambientes (DEV, Staging e Produção) num cluster Kubernetes (k3s), com CI/CD GitOps completo (GitHub Actions + GHCR + Argo CD), segredos 100% fora do repositório via Infisical e ingresso seguro por Cloudflare Tunnel. Detalhes e URLs na seção 4.
| Serviço | Projeto | Responsabilidade principal | Porta HTTPS (dev) | Porta HTTP (dev) |
|---|---|---|---|---|
| Auth API | src/MBA.Auth.Api |
Cadastro/login de usuário, emissão de JWT, Identity | https://localhost:7163 |
http://localhost:5020 |
| Aluno API | src/MBA.Aluno.API |
Aluno, matrícula, progresso de aulas, conclusão de curso | https://localhost:7124 |
http://localhost:5236 |
| Conteúdo API | src/MBA.Conteudo.Api |
Cursos, aulas, categorias | https://localhost:7285 |
http://localhost:5137 |
| Pagamentos API | src/MBA.Pagamentos.Api |
Faturamento, transações, integração de gateway | https://localhost:7171 |
http://localhost:5190 |
| BFF API | src/MBA.Bff.Api |
Orquestração de chamadas, agregação para o front-end | https://localhost:7119 |
http://localhost:5289 |
Projetos de suporte:
src/MBA.Core— utilitários transversais: Mediator in-process, eventos de integração, base de Domain Notifications, claims principal.src/MBA.WebApi.Core— extensões reutilizáveis para Identity, JWT, CORS, Swagger e Polly.src/MBA.MessageBus— wrapper sobre EasyNetQ (IMessageBus.PublishAsync,SubscribeAsync).src/MBA.<Contexto>.Application/.Data/.Domain— camadas internas por bounded context (Command/Query handlers, DbContext, entidades e regras de domínio).
flowchart TD
MVC["WebApp.MVC (front-end)"] --> BFF["BFF API<br/>Refit + Polly (retry + circuit breaker)"]
BFF --> AUTH["Auth API"]
BFF --> ALU["Aluno API"]
BFF --> CON["Conteudo API"]
BFF --> PAG["Pagamentos API"]
ALU -->|"HTTP sync: valida curso ativo"| CON
PAG -->|"HTTP sync: valida pagamento"| ALU
PAG -->|"publish PagamentoConfirmado/Recusado"| MQ[("RabbitMQ")]
MQ -->|"subscribe: ativa matricula"| ALU
AUTH --- DBA[("SQL Server<br/>auth")]
ALU --- DBL[("SQL Server<br/>aluno")]
CON --- DBC[("SQL Server<br/>conteudo")]
PAG --- DBP[("SQL Server<br/>pagamentos")]
+----------------------+
| Front-end / |
| WebApp.MVC |
+----------+-----------+
|
v
+----------------------+
| BFF API |
| (Refit/IHttpFactory |
| + Polly) |
+---+---+---+---+------+
| | | |
+-----------------+ | | +-----------------+
v v v v
+-----------------+ +----------------+ +------------------+
| Auth API | | Aluno API | | Conteúdo API |
+-----------------+ +----------------+ +------------------+
|
| HTTP sync
v
+-----------------+
| Pagamentos API |
+--------+--------+
|
| publish PagamentoConfirmado/Recusado
v
+-----------------+
| RabbitMQ | <--- subscribe Aluno API
+-----------------+
- Síncrono (HTTP): BFF → Auth/Aluno/Conteúdo/Pagamentos. Aluno → Conteúdo (validar curso ativo na matrícula). Pagamentos → Aluno (validar
PagamentoPodeSerRealizado). - Assíncrono (RabbitMQ): Pagamentos publica
PagamentoConfirmadoEvent/PagamentoRecusadoEvent; Aluno consome e atualiza o status da matrícula.
Além do ambiente local de desenvolvimento, a plataforma roda publicada e 100% automatizada em três ambientes num cluster Kubernetes (k3s) hospedado na Hetzner, com todos os segredos gerenciados pelo Infisical e a esteira de deploy operando no modelo GitOps pull-based com Argo CD.
| Aplicação | DEV | Staging | Produção |
|---|---|---|---|
| Web (Portal do Aluno) | dev-mba-store.dots.dev.br | stg-mba-store.dots.dev.br | mba-store.dots.dev.br |
| BFF | dev-mba-store-bff.dots.dev.br | stg-mba-store-bff.dots.dev.br | mba-store-bff.dots.dev.br |
| Identidade (Auth) | dev-mba-auth-api.dots.dev.br | stg-mba-auth-api.dots.dev.br | mba-auth-api.dots.dev.br |
| Alunos | dev-mba-aluno-api.dots.dev.br | stg-mba-aluno-api.dots.dev.br | mba-aluno-api.dots.dev.br |
| Conteúdo | dev-mba-conteudo-api.dots.dev.br | stg-mba-conteudo-api.dots.dev.br | mba-conteudo-api.dots.dev.br |
| Financeiro (Pagamentos) | dev-mba-financeiro-api.dots.dev.br | stg-mba-financeiro-api.dots.dev.br | mba-financeiro-api.dots.dev.br |
Cada ambiente vive num namespace próprio do cluster (mba-modulo4-dev, mba-modulo4 e mba-modulo4-prd), com RabbitMQ dedicado e bancos SQL Server isolados por serviço e por ambiente (mba-{serviço}-{dev|staging|prd}).
- Chave JWT, credenciais do RabbitMQ e connection strings não existem mais no código nem no histórico de configuração: vivem num cofre Infisical self-hosted (
infisical.dots.dev.br), separadas por ambiente. - No cluster, o Infisical Secrets Operator sincroniza o cofre para Secrets do Kubernetes e dispara rolling restart automático dos Deployments quando um segredo muda (annotation
secrets.infisical.com/auto-reload). - Cada API valida os segredos obrigatórios no startup (fail-fast): sem eles, a aplicação nem sobe e explica exatamente o que falta e como configurar.
- No desenvolvimento local, o profile
Infisical (dev)injeta os segredos no F5 via Infisical CLI (ver seção 5). - Ressalva de transparência: os manifestos de bootstrap local —
docker-compose.ymlek8s/base/secret.yaml(trilha local com kind, fora da esteira Argo) — usam valores de exemplo (JWT de dev, senha do SA local), não os segredos reais dos ambientes publicados. A afirmação de "sem segredos" vale para dev/staging/prd via Infisical.
merge na develop ──► GitHub Actions
├─ CI: build + testes + cobertura (gate) + lint + scan de vulneráveis (.NET 8)
└─ CD: builda as 6 imagens ──► GHCR (ghcr.io, imagens privadas)
└─ atualiza k8s/dev/ com a nova tag [skip ci]
│
▼
Argo CD (roda DENTRO do k3s)
detecta o commit e sincroniza
│
▼
namespace mba-modulo4-dev (ambiente DEV)
merge na master ──► mesmo fluxo com tags stg-<sha> e k8s/staging/ ──► ambiente Staging
│
▼
⏸ GATE DE PRODUÇÃO (aprovação manual)
revisor humano valida o Staging publicado
│ botão "Review deployments" ► Approve
▼
bump de k8s/prd/ ──► ambiente Produção
(promove a MESMA imagem validada no staging — sem rebuild)
- O GitHub nunca acessa o cluster: o Argo CD observa o repositório e puxa as mudanças (GitOps pull-based). Nenhuma credencial de cluster existe fora dele.
- As imagens são publicadas no GitHub Container Registry usando apenas o
GITHUB_TOKENnativo do Actions (zero secrets manuais na esteira) e puxadas pelo cluster viaimagePullSecret. - Sync automático com
pruneeselfHeal: o estado do cluster converge sempre para o que está no git. Rollback =git revert. - Portões de qualidade no CI: cada push/PR roda testes com cobertura (
XPlat Code Coverage+ ReportGenerator) e um gate de cobertura mínima, além de lint (dotnet format) e varredura de dependências vulneráveis (dotnet list package --vulnerable). O.github/dependabot.ymlabre PRs semanais de atualização (NuGet, Docker e GitHub Actions). A análise estática roda no workflowsonarcloud.yml(SonarCloud), consumindo a cobertura de testes — requer o secretSONAR_TOKEN(o job se auto-pula enquanto o token não estiver configurado). - Gate de produção com aprovação manual: o job
promote-prdé vinculado ao GitHub Environmentproducao, protegido por required reviewers. O Staging publica automaticamente a cada merge na master; a Produção só é promovida depois que um revisor valida o Staging no ar e aprova o deploy (Actions → Review deployments). A aba Environments do repositório registra o histórico de quem aprovou cada promoção. - Política de aprovação: seis membros do time são revisores do environment
producaoe basta a aprovação de um deles para promover; com prevent self-review habilitado, quem disparou o deploy não pode aprovar a si mesmo — garantindo sempre um segundo par de olhos entre o merge e a produção.
- k3s (Kubernetes) num servidor Hetzner; a API do cluster é restrita por firewall.
- Ingresso público exclusivamente via Cloudflare Tunnel (containers
cloudflareddentro do cluster fazem conexão de saída): nenhuma porta de aplicação aberta no servidor, TLS e proteção DDoS na borda da Cloudflare. - Painéis de operação, protegidos por Cloudflare Access (login por One-Time PIN no e-mail autorizado):
- Argo CD (estado dos deploys, diff, histórico e rollback):
k3s-argocd.dots.dev.br - Headlamp (pods, logs, eventos e recursos do cluster):
k3s-panel.dots.dev.br
- Argo CD (estado dos deploys, diff, histórico e rollback):
- Schema e seed dos bancos são criados automaticamente em Development/Staging no startup (
EnsureCreatedno SQL Server); Production não cria schema nem seed por design — os bancosmba-*-prdforam inicializados uma única vez, de forma controlada, e a aplicação em produção apenas os consome.
Para facilitar a consulta e a correção deste trabalho acadêmico, o Swagger é exposto em dev, staging e produção — habilitado explicitamente por SWAGGER_ENABLED=true nos ConfigMaps dos três ambientes. O default do código é seguro: o Swagger só é servido em Development ou quando SWAGGER_ENABLED=true; em qualquer ambiente publicado sem o flag, fica desligado. Cada Swagger carrega um aviso explicando a decisão acadêmica de mantê-lo aberto aqui.
- Escalabilidade: os servicos de aplicacao rodam com
replicas: 2em Staging e Producao e um HorizontalPodAutoscaler por servico (CPU alvo ~70%, minimo 2 / maximo 4).resources.requests/limitsde CPU e memoria estao definidos em todos os Deployments — pre-requisito do HPA e protecao contra um servico consumir todo o node. - Resiliencia: o BFF e as chamadas sincronas entre servicos (Pagamentos -> Aluno, Aluno -> Conteudo) usam retry com backoff + circuit breaker (Polly), evitando falha em cascata quando um servico degrada.
- Startup seguro: cada Deployment tem
startupProbe(alem de liveness/readiness) para tolerar a migracao/seed inicial sem cair em loop de reinicio em banco frio. - Hardening: todos os Pods tem
securityContext(runAsNonRoot,allowPrivilegeEscalation: false,capabilities: drop [ALL], seccompRuntimeDefault) eautomountServiceAccountToken: false— o cluster impoe o nao-root, nao apenas o Dockerfile. - Observabilidade: logs estruturados em JSON e metricas Prometheus em
/metricsnos seis servicos.
- .NET 8 SDK (obrigatório — todos os projetos usam
TargetFramework net8.0). - RabbitMQ 3.x acessível em
localhost:5672com credenciaisguest/guest(padrão Development). Recomendado via Docker:docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management. - SQLite (usado em Development — arquivos
.dbgerados automaticamente em cada serviço). SQL Server opcional para Production (controlado porDatabase__Provider=SqlServer). - IDE de sua preferência: Visual Studio 2022 17.10+, JetBrains Rider ou VS Code + C# Dev Kit.
- Git para clonar o repositório.
OPÇÃO 1 (recomendada) — INSTALAR O INFISICAL CLI:
winget install infisicalinfisical login --domain=https://infisical.dots.dev.br- No Visual Studio, selecione o profile
Infisical (dev)e rode (F5). Ou no terminal:infisical run --env=dev -- dotnet run --project src/MBA.<Serviço>OPÇÃO 2 — CONFIGURAR MANUALMENTE (sem Infisical): preencha as chaves via
dotnet user-secrets set "AppSettings:Secret" "<valor>"(e demais) ou noappsettings.Development.json.SEM UMA DESSAS, A APLICAÇÃO PARA NO STARTUP com uma mensagem explicando exatamente o que falta (validação fail-fast). Não é bug — é proteção para não rodar com segredo faltando.
Cada serviço mantém sua própria connection string em appsettings.Development.json:
- Auth API:
ConnectionStrings:DefaultConnection→Data Source=Data/AuthDB.db - Aluno API:
ConnectionStrings:ConnectionSqliteAluno→AlunoDB.db - Conteúdo API:
AppSettings:DatabaseSettings:ConnectionStringConteudo→Data Source=Data\ConteudoDB.db - Pagamentos API: SQLite resolvido em runtime por
SqlitePathResolver(Development).
O provider de banco é decidido pelo ambiente: Development = SQLite; Staging/Production = SQL Server (com as connection strings vindas do Infisical). Para forçar SQL Server localmente, defina a variável DATABASE_PROVIDER=SqlServer — em builds DEBUG a connection string é substituída automaticamente por (localdb)\MSSQLLocalDB, protegendo quem não tem acesso ao servidor publicado.
Todas as APIs que publicam ou consomem eventos usam a mesma connection string em MessageQueueConnection:MessageBus:
host=localhost:5672;publisherConfirms=true;timeout=30;username=guest;password=guest
O
guest/guestvale apenas para o RabbitMQ local. Nos ambientes publicados a credencial é forte, exclusiva por ambiente e vem do Infisical.
Chave simétrica compartilhada entre Auth/Aluno/Conteúdo/Pagamentos em AppSettings (ex.: Secret, ExpiracaoHoras, Emissor, ValidoEm). A chave foi rotacionada e removida do repositório: em todos os cenários ela vem do Infisical (localmente via profile Infisical (dev); no cluster via Secrets Operator).
AppServicesSettings no appsettings.Development.json do BFF mapeia os serviços (ajustar se as portas forem alteradas):
AlunoUrl = https://localhost:7124
ConteudoUrl = https://localhost:7285/
PagamentoUrl = https://localhost:7171
AutenticacaoUrl = https://localhost:7163/
FaturamentoUrl = https://localhost:7171/
A ordem recomendada para o ambiente local é:
- RabbitMQ (precisa estar online antes de qualquer API que publique/consuma eventos).
- Auth API — gera tokens usados pelas demais.
- Conteúdo API — catálogo de cursos, pré-requisito para matrícula.
- Aluno API — matrícula/progresso; depende de Conteúdo e escuta eventos do broker.
- Pagamentos API — depende de Aluno (validação) e publica eventos após processar.
- BFF API — porta de entrada para o front-end; depende de todas as anteriores.
Migrations e seeds são executados automaticamente no startup (quando aplicável) via CarregamentoDadosAsync() e helpers de DatabaseSelector.
# Restaurar e compilar toda a solution
dotnet restore MBA.Modulo4.sln
dotnet build MBA.Modulo4.sln
# Subir cada serviço (em terminais separados)
dotnet run --project src/MBA.Auth.Api
dotnet run --project src/MBA.Conteudo.Api
dotnet run --project src/MBA.Aluno.API
dotnet run --project src/MBA.Pagamentos.Api
dotnet run --project src/MBA.Bff.ApiSwagger disponível em cada serviço na rota /swagger (Development).
docker compose up -d --buildSobe o RabbitMQ e todos os serviços em containers non-root, com healthchecks em
/health/live e /health/ready em todas as APIs. As URLs internas entre serviços
(Aluno → Conteúdo, Pagamentos → Aluno) já estão configuradas por variável de ambiente.
Valida o fluxo completo — registro, login, catálogo, matrícula, pagamento e confirmação assíncrona via RabbitMQ — contra o ambiente Docker:
# Script bash: sobe o ambiente via docker compose e executa o fluxo
bash scripts/smoke-test.sh # sobe o ambiente e deixa no ar ao final
bash scripts/smoke-test.sh --skip-up # usa um ambiente já em execução
bash scripts/smoke-test.sh --down # derruba o ambiente (down -v) ao final
bash scripts/smoke-test.sh --timeout 90 # tempo máximo (s) do polling da confirmação
# Testes xUnit de smoke (skipados por padrão para não afetar o CI; exigem ambiente no ar)
EXECUTAR_SMOKE_TESTS=true dotnet test src/MBA.SmokeTests -c ReleaseO script termina com os blocos RELATORIO (PASS/FAIL/WARN por passo) e FINDINGS
(divergências detectadas em runtime); exit 0 indica fluxo íntegro. As URLs dos serviços
podem ser sobrescritas pelas variáveis SMOKE_AUTH_URL, SMOKE_CONTEUDO_URL,
SMOKE_ALUNO_URL, SMOKE_PAGAMENTOS_URL e SMOKE_BFF_URL. No GitHub Actions, o
workflow smoke-test.yml executa o mesmo fluxo sob demanda (workflow_dispatch). Importante: os gatilhos automáticos de push/pull_request estão desativados nesse workflow, então o smoke E2E não faz parte da validação automática da esteira hoje — rode-o manualmente (Actions -> Smoke Test -> Run workflow) antes de promoções relevantes.
Para rodar os testes, gerar cobertura e reproduzir localmente a esteira do CI (build,
testes e gate de cobertura) e a análise do SonarCloud, use os scripts documentados em
scripts/README.md:
.\scripts\ci-local.ps1 # build + testes + cobertura + gate (espelha o ci.yml)
.\scripts\sonar-local.ps1 -Org <org> -Key <key> # análise SonarCloud (requer SONAR_TOKEN) Usuário BFF Auth Aluno Conteúdo Pagamentos RabbitMQ
| | | | | | |
|--register-->|--POST------->| | | | |
| | |---JWT------>| | | |
|<--token-----| | | | | |
| | | | | | |
|--matricula->|--POST------->| |--valida---->| | |
| | | |<--curso ok--| | |
| | | |--cria matr--| | |
| | | | (PendentePagamento) | |
| | | | | | |
|--pay------->|--POST---------------------->| |--registra->| |
| | | | | |--publica-->|
| | | |<---------PagamentoConfirmadoEvent------|
| | | |--atualiza | | |
| | | | status | | |
| | | |(PagamentoRealizado) | |
- Aluno assiste uma aula → BFF/Aluno API recebe
RegistrarAulaAssistidaCommand→ gravaProgressoAula. AlunoQueryServicecalculatotalAulas,totalAssistidaseaulasFaltantescom base em Conteúdo API + ProgressoAula local.- Quando
aulasFaltantes == 0,ConcluirCursoCommandHandlermarca a matrícula como concluída e (opcionalmente) emite evento de conclusão.
Cada bounded context segue a convenção:
src/
MBA.<Contexto>.Api -> projeto ASP.NET Core (Controllers, Configuration, Program)
MBA.<Contexto>.Application -> Commands, Queries, Handlers, Validators, DTOs
MBA.<Contexto>.Domain -> Entidades, Value Objects, regras de domínio
MBA.<Contexto>.Data -> DbContext, Mappings, Migrations, Seed
Projetos compartilhados:
src/
MBA.Core -> Mediator, Events, Messages, AppIdentityUser
MBA.WebApi.Core -> JWT, Swagger, Polly, CORS, DatabaseSelector
MBA.MessageBus -> IMessageBus (EasyNetQ wrapper)
- RabbitMQ offline → publicação falha silenciosamente e consumers não sobem. Suba o container antes das APIs e confira o painel em
http://localhost:15672(guest/guest). - Porta em uso → ajuste
applicationUrlemProperties/launchSettings.jsondo serviço em conflito (e atualizeAppServicesSettingsdo BFF). - Migrations não aplicadas → cada API roda a criação automática no startup. Em caso de schema corrompido no SQLite local, remova o arquivo
.dbdo serviço e reinicie. - 401 em rotas protegidas → confira se o JWT está no header
Authorization: Bearer <token>e se aSecreté igual em todas as APIs. - Fluxo de pagamento não ativa matrícula → verifique (a) se
PagamentoConfirmadoEventfoi publicado emIMessageBus.PublishAsync, (b) se o consumer da Aluno API está registrado comoHostedService/subscriber e (c) se a fila existe no RabbitMQ. - BFF retorna
BaseAddress is null→ verifique seAppServicesSettings.AutenticacaoUrl(e demais URLs) estão preenchidas também noappsettings.jsonbase, não apenas em Development.
Projeto acadêmico do MBA DevXpert Full Stack .NET — Módulo 5. Não aceita contribuições externas. Dúvidas ou feedbacks pelo recurso de Issues. O arquivo FEEDBACK.md é de uso exclusivo do instrutor.