Skip to content

Repository files navigation

InvestOps Backoffice

CI

Plataforma fullstack de backoffice para operações de investimento

Status: MVP funcionalmente completo — todas as fases do roadmap entregues (autenticação fica fora do MVP por decisão, ADR 0006). O SPEC.md descreve a visão completa; as evoluções mapeadas estão nos ADRs.

⚠️ Demo local — não exponha na internet. A stack sobe com credenciais de demonstração e publica Postgres/RabbitMQ no host para facilitar o onboarding; não há autenticação por decisão de escopo (ADR 0006). Use apenas na máquina local — qualquer deploy real exige sobrescrever segredos, fechar as portas e colocar auth na frente.

Destaques de engenharia

  • Arquitetura event-driven com Outbox Pattern nos dois lados — API .NET e Worker Go publicam via relay transacional (sem dual-write) por RabbitMQ.
  • Resiliência no consumo: retry com backoff (wait queue + TTL) e Dead Letter Queue; falhas transitórias reprocessam, falhas de negócio são terminais.
  • Interop poliglota: serviço de cotações em gRPC (Go) consumido pela API .NET, com Protobuf versionado.
  • Modular monolith .NET 8 (Clean Architecture, Dapper, DbUp) + SPA Angular 21 zoneless (signals, standalone, OnPush, sem zone.js) servida por edge nginx same-origin.
  • Qualidade: testes com Testcontainers, CI no GitHub Actions, config por ambiente, rate limiting por perfil de endpoint, 9 ADRs (índice) e specs de design por fase em docs/.

Interface

Prints da aplicação — todos os dados são fictícios (seed local).

Dashboard

Dashboard — métricas do pipeline, funil de status e auto-refresh.

Operações Conciliação
Operações Conciliação
Import com validação, idempotência e status por operação. Extrato do custodiante: divergências, resolver/ignorar.
Processing Monitor Produtos
Processing Monitor Produtos
Eventos do worker com tentativas/erro reais e auto-refresh. CRUD com ticker único e exclusão lógica (linha inativa esmaecida).

Status & roadmap

O SPEC.md é a visão completa; a entrega é incremental, por fases (docs/design/):

Área Status
Import de operações (validação, idempotência, Outbox → RabbitMQ → Worker) (ADR 0003)
Worker Go: regras de negócio, posição consolidada, retry + DLQ (ADR 0004)
Read API: operations, positions, dashboard, lookups (paginação/ordenação server-side)
Frontend: dashboard, operações, posições, cotações ao vivo
Cotações via gRPC (.NET ↔ Go) + SSE
Edge nginx (same-origin) + rate limiting
Conciliação com custodiante: import CSV, divergências, resolver/ignorar (ADR 0007)
CRUD de produtos (POST/PUT/DELETE lógico, ticker único)
Processing monitor: eventos do worker + falha terminal com tentativas/erro (ADR 0008)
Autenticação/autorização (OIDC) 🚫 fora do MVP — ADR 0006

Arquitetura

flowchart LR
    SPA["Angular 21 SPA<br/>zoneless"]
    EDGE["edge nginx<br/>same-origin"]
    API[".NET 8 API<br/>modular monolith"]
    WK["Go Worker<br/>retry + DLQ"]
    MD["Go MarketData<br/>gRPC"]
    MQ{{RabbitMQ}}
    PG[("PostgreSQL")]

    SPA <-->|"/ e /api"| EDGE
    EDGE --> API
    API -->|"operação + outbox<br/>(mesma transação)"| PG
    API -.->|"Outbox relay · source=api"| MQ
    MQ -->|OperationImported| WK
    WK -->|"posições + processing_events"| PG
    WK -.->|"Outbox relay · source=worker"| MQ
    API <-->|gRPC| MD
Loading

Quatro serviços — SPA Angular, API .NET, Worker Go e MarketData Go — sobre banco compartilhado no MVP (ADR 0005). Integração assíncrona via Outbox Pattern nos dois lados (ADR 0003) + RabbitMQ; cotações via gRPC e stream ao vivo por SSE. No deploy, um edge nginx serve o SPA e faz proxy de /api para a API (same-origin). Todas as decisões — com limitações e evolução — em docs/adr/.

Stack

Camada Tecnologias
Frontend Angular 21 (zoneless, signals), TypeScript, RxJS, Angular Material, Jest
API .NET 8, ASP.NET Core, Dapper, FluentValidation, Serilog, OpenTelemetry
Worker Go, RabbitMQ, pgx, slog
MarketData Go, gRPC, Protobuf (buf)
Infra Docker Compose, PostgreSQL, RabbitMQ, nginx

Estrutura do repositório

investops-backoffice/
  backend/                      # solução .NET 8
    src/
      InvestOps.Api/            # ASP.NET Core (Serilog, Swagger, Health Checks)
      InvestOps.Application/    # casos de uso
      InvestOps.Domain/         # entidades e enums do domínio
      InvestOps.Infrastructure/ # persistência/Dapper, RabbitMQ, cliente gRPC
      InvestOps.Contracts/      # DTOs de evento
      InvestOps.Migrator/       # DbUp: schema + seed
    tests/                      # UnitTests, IntegrationTests
  worker-go/                    # worker assíncrono (consumo RabbitMQ, retry + DLQ)
  marketdata-go/                # serviço gRPC de cotações
  frontend/investops-web/       # app Angular 21 (zoneless)
  contracts/                    # contratos de eventos JSON + .proto (gRPC)
  docs/                         # ADRs e documentação de design
  docker-compose.yml            # stack completo

Como rodar

Há dois caminhos:

  • A. Stack completo via Docker — um comando sobe todos os serviços. Ideal para ver o sistema fim a fim.
  • B. Componente por componente — infra no Docker, cada serviço no host, com hot-reload. Ideal para desenvolver.

Pré-requisitos


A. Stack completo via Docker Compose

Builda e sobe todos os serviços: postgres, rabbitmq, migrator (schema + seed), marketdata, worker, api e web.

cp .env.example .env      # opcional; o compose já usa defaults
docker compose --env-file .env.example up -d --build
docker compose --env-file .env.example ps      # aguarde postgres/rabbitmq (healthy)

Parar tudo:

docker compose --env-file .env.example down        # mantém os dados (volumes)
docker compose --env-file .env.example down -v      # apaga os dados

B. Componente por componente (desenvolvimento)

Suba apenas a infra pelo Docker e rode cada serviço no host. Os defaults de cada serviço já apontam para localhost, então nenhuma variável de ambiente é obrigatória em dev. Siga a ordem abaixo (a API depende de MarketData; o Worker e a API dependem de Postgres + RabbitMQ).

1. Infraestrutura (só PostgreSQL + RabbitMQ)

docker compose --env-file .env.example up -d postgres rabbitmq

Sem os nomes dos serviços, docker compose up subiria tudo (inclusive api/worker/web já buildados).

2. Schema do banco (Migrator)

cd backend
dotnet run --project src/InvestOps.Migrator -- --seed   # schema + dados de exemplo
# ou apenas o schema:
dotnet run --project src/InvestOps.Migrator

3. MarketData (gRPC de cotações)

cd marketdata-go
go run ./cmd/server        # escuta gRPC em :5300 (env GRPC_PORT)

4. Worker (processamento assíncrono)

cd worker-go
go run ./cmd/worker        # consome RabbitMQ, aplica regras e faz o relay do Outbox

Retry/backoff/DLQ e demais variáveis estão em worker-go/README.md.

5. API .NET

cd backend
dotnet run --project src/InvestOps.Api

Em Production o Swagger é desabilitado; apenas /health fica exposto.

6. Frontend Angular

cd frontend/investops-web
npm install
npm start                  # ng serve em http://localhost:4200

Em dev o SPA chama a API direto em http://localhost:5000/api (src/environments/environment.ts).


Ambientes (API)

A configuração é separada por ambiente via ASPNETCORE_ENVIRONMENT, carregando appsettings.{Environment}.json sobre o appsettings.json base.

Ambiente Arquivo Serilog Swagger
Development appsettings.Development.json Debug
Staging (homologação) appsettings.Staging.json Information
Production appsettings.Production.json Warning

Rodar em um ambiente específico (perfis em launchSettings.json):

dotnet run --project src/InvestOps.Api --launch-profile staging
dotnet run --project src/InvestOps.Api --launch-profile production
# ou direto:
ASPNETCORE_ENVIRONMENT=Staging dotnet run --project src/InvestOps.Api

Segredos (Staging/Production) não ficam nos arquivos — vêm de variáveis de ambiente, que sobrescrevem o appsettings (__ separa seções):

export ConnectionStrings__Postgres="Host=...;Database=...;Username=...;Password=..."
export RabbitMq__Host="rabbit.prod.interno"
export RabbitMq__Username="..."
export RabbitMq__Password="..."
export MarketData__Url="http://marketdata.prod.interno:5300"

Segurança & escopo

Decisões de segurança do MVP — registradas em ADR 0006:

  • Rate limiting nativo (ASP.NET Core): limite global de 100 req/min + política mais rígida (20 req/min) nos endpoints de escrita (import, reprocess); health checks isentos; 429 com Retry-After. Limites configuráveis via seção RateLimiting.
  • Autenticação/autorização: fora do escopo do MVP, por decisão — backoffice interno assume rede privada + IdP corporativo; o ADR documenta como o design acomoda [Authorize] + policies por perfil.
  • SQL 100% parametrizado (Dapper) com ordenação dinâmica por whitelist; Swagger desabilitado em Production (com teste garantindo); security headers no nginx.
  • Credenciais: o appsettings.json base não contém credenciais — as de dev local vivem em appsettings.Development.json, e Staging/Production só via variáveis de ambiente. Os defaults investops/investops do .env.example/compose são intencionais e exclusivos de demo local (docker compose up sem edição é requisito); qualquer deploy real deve sobrescrevê-los.

Testes

# Backend (.NET) — unit + integração (testcontainers exige Docker)
cd backend && dotnet test

# Worker e MarketData (Go) — integração usa testcontainers (exige Docker)
cd worker-go && go test ./...
cd marketdata-go && go test ./...

# Frontend (Angular) — Jest
cd frontend/investops-web && npm test

Portas

Serviço Dev (host) Docker
App — edge nginx (SPA + /api) 8080
.NET API 5000 interna (via edge)
Angular dev server 4200
MarketData (gRPC) 5300 5300
PostgreSQL 5432 5432
RabbitMQ (AMQP) 5672 5672
RabbitMQ Management UI 15672 15672

Trade-offs & próximos passos

Decisões conscientes de MVP, com o caminho de evolução registrado nos ADRs:

  • Autenticação/autorização (OIDC) — fora do MVP por decisão; o design acomoda [Authorize] + policies por perfil (ADR 0006).
  • Banco compartilhado → database-per-service — um PostgreSQL entre API e worker hoje; a fronteira por módulo já isola o caminho de separação (ADR 0005).
  • Dead-letter do Outbox — ambos os publishers reconectam após queda; falta cap + dead-letter de mensagem "poison" — assimetria consciente com o consumer, que já tem DLQ (ADR 0009).
  • Conciliação síncrona → assíncrona — comparação no request hoje; extratos grandes pedem processamento em background (ADR 0007).
  • Rate limiting por IP → por identidade — hoje particiona por IP; com auth, migra para X-Forwarded-For/subject (ADR 0006).

Fluxo de desenvolvimento

  • main — estável (release por fase)
  • developer — integração
  • feature/* — uma branch por task, PR para developer

Commits seguem Conventional Commits.

About

Plataforma fullstack de backoffice de investimentos (estudo): .NET 8 API + Go worker + Angular + RabbitMQ + PostgreSQL

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages