Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7fc291e
Rename devops-flow.png.png to devops-flow.png
lmoraesdev May 4, 2026
d34e607
Merge pull request #1 from lmoraesdev/develop
lmoraesdev Jun 3, 2026
5d35df0
Merge pull request #2 from lmoraesdev/develop
lmoraesdev Jun 3, 2026
cb05b76
Merge pull request #3 from lmoraesdev/develop
lmoraesdev Jun 3, 2026
8ef055e
refactor(logging): migrate to Spring Boot structured logging (ECS)
lmoraesdev Jun 3, 2026
049241f
Merge branch 'main' of https://github.com/lmoraesdev/java-payment-hex…
lmoraesdev Jun 3, 2026
0b21598
feat(domain): add Money value object with validation
lmoraesdev Jun 4, 2026
08596b1
feat(domain): add ChargeStatus enum
lmoraesdev Jun 4, 2026
dc7d8b1
feat(domain): add Charge entity with create factory and id-based equals
lmoraesdev Jun 4, 2026
7f98dda
feat(persistence): add ChargeRepository output port
lmoraesdev Jun 4, 2026
8f9e97a
feat(persistence): add ChargeJpaEntity mapped to charges table
lmoraesdev Jun 4, 2026
3542c78
feat(persistence): add Spring Data JPA repository
lmoraesdev Jun 4, 2026
9eb468b
feat(persistence): add ChargeMapper between domain and entity
lmoraesdev Jun 4, 2026
8affaeb
feat(persistence): implement ChargeRepository via JPA adapter
lmoraesdev Jun 4, 2026
941957c
chore(config): tune CI, ECS logging, OTLP tracing and disable redis r…
lmoraesdev Jun 4, 2026
92484f4
chore: remove placeholder .gitkeep files and stale devops image
lmoraesdev Jun 4, 2026
0539f6c
feat(application): add CreateCharge contract, command and result
lmoraesdev Jun 4, 2026
3857387
feat(application): implement CreateChargeService
lmoraesdev Jun 4, 2026
d729f02
feat(web): add create charge request and response DTOs
lmoraesdev Jun 4, 2026
1b4afd9
feat(web): add POST /charges endpoint
lmoraesdev Jun 4, 2026
989e160
chore: remove orphaned .gitkeep placeholders from non-empty packages
lmoraesdev Jun 5, 2026
984ae63
chore(build): add datafaker dependency and JaCoCo coverage reporting
lmoraesdev Jun 5, 2026
c0d0c9c
style: apply Spotless Google Java Format across all sources
lmoraesdev Jun 5, 2026
cd7b154
test(support): add integration test base class, ChargeTestData builde…
lmoraesdev Jun 5, 2026
506a603
feat(application): add business-event logging to CreateChargeService
lmoraesdev Jun 5, 2026
b6f125f
chore(web): enable RFC 9457 problem details
lmoraesdev Jun 5, 2026
aed535a
fix(web): return problem details with correct status per error type (…
lmoraesdev Jun 5, 2026
b62e411
test(support): add shared Testcontainers config and integration base
lmoraesdev Jun 5, 2026
1224b42
test(domain): cover Money validation and Charge identity
lmoraesdev Jun 5, 2026
2ff3cf9
test(application): unit test CreateChargeService with mocked repository
lmoraesdev Jun 5, 2026
2358f4f
test(persistence): add ChargeRepositoryIT round-trip with Testcontainers
lmoraesdev Jun 5, 2026
103675d
test(web): integration test POST /charges (201 and 400 problem details)
lmoraesdev Jun 5, 2026
f8fb4df
refactor(domain): remove unused PENDING status (charge starts ACTIVE)
lmoraesdev Jun 5, 2026
db2ec0f
chore(test): make test profile self-sufficient with problemdetails en…
lmoraesdev Jun 5, 2026
429e7e0
feat(domain): add InvalidAmountException; Money throws domain excepti…
lmoraesdev Jun 5, 2026
401b6d6
test(domain): update assertions to InvalidAmountException after Money…
lmoraesdev Jun 5, 2026
13496cb
Merge pull request #5 from lmoraesdev/feature/charge-domain
lmoraesdev Jun 5, 2026
3ff1696
fix(style): resolve Checkstyle violations in ChargeJpaEntity and Charge
lmoraesdev Jun 5, 2026
89d3c13
test(support): align Testcontainers Postgres version with production …
lmoraesdev Jun 5, 2026
09b0519
Merge pull request #6 from lmoraesdev/feature/charge-domain
lmoraesdev Jun 5, 2026
d018ba7
feat(docs): add SpringDoc OpenAPI 2.8.17 with Swagger UI
lmoraesdev Jun 5, 2026
3928193
docs: update README to reflect EPIC-001 completion
lmoraesdev Jun 5, 2026
e164271
fix(observability): set application name for Prometheus and Jaeger
lmoraesdev Jun 6, 2026
b0c2006
refactor(domain): store monetary amounts as BIGINT centavos
lmoraesdev Jun 6, 2026
ac6d5b1
Merge pull request #7 from lmoraesdev/feature/charge-domain
lmoraesdev Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [develop, main]
branches: ["epic/**", develop, main]
pull_request:
branches: [main]

Expand Down
142 changes: 83 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
# payment-api

Fundação de um core de pagamentos Pix em Java — infraestrutura, observabilidade e pipeline CI/CD prontos; domínio em construção.
Core de pagamentos Pix em Java — Hexagonal Architecture, observabilidade e pipeline CI/CD. EPIC-001 (criar cobrança) implementado e testado.

[![CI](https://github.com/lmoraesdev/java-payment-hexagonal/actions/workflows/ci.yml/badge.svg)](https://github.com/lmoraesdev/java-payment-hexagonal/actions/workflows/ci.yml)
![Java](https://img.shields.io/badge/Java-21-blue?logo=openjdk&logoColor=white)
![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.3-6DB33F?logo=springboot&logoColor=white)

## Status

A plataforma está completa: serviços sobem, métricas chegam no Grafana, traces no Jaeger, testes de integração passam com Testcontainers. O que ainda não existe é o domínio de negócio — entidades, casos de uso, portas e adapters de persistência/mensageria. Esses são os próximos passos (ver [Roadmap](#roadmap)).
| EPIC | Descrição | Status |
|---|---|---|
| EPIC-001 | Criar cobrança — domínio, persistência, REST, tratamento de erros, testes | ✅ Concluído |
| EPIC-002 | Buscar cobrança (`GET /charges/{id}`) + idempotência | 🔲 Roadmap |
| EPIC-003 | Publicar `ChargeCreated` no Kafka | 🔲 Roadmap |
| EPIC-008 | Flyway — migrações de schema versionadas | 🔲 Roadmap |

## Stack

Expand All @@ -19,112 +24,143 @@ A plataforma está completa: serviços sobem, métricas chegam no Grafana, trace
| PostgreSQL | 18 | Persistência principal |
| Kafka (KRaft) | 3.9 | Event streaming — sem Zookeeper |
| Redis | 7 | Cache / idempotência (pré-instalado, profile `cache`) |
| SpringDoc OpenAPI | 2.8.17 | Swagger UI + spec OpenAPI 3 |
| Prometheus + Grafana | latest | Métricas + dashboard Payment Overview pré-provisionado |
| Jaeger + OpenTelemetry | latest | Distributed tracing via OTLP HTTP |
| Testcontainers | 1.21 | Testes de integração com banco real |
| Testcontainers | 1.21 | Testes de integração com PostgreSQL 18 real |
| Spotless (GJF AOSP) | 2.43 | Formatação automática de código |
| Checkstyle | 3.5 | Verificação de estilo |

## Arquitetura

O projeto segue Arquitetura Hexagonal (Ports & Adapters): o domínio não conhece Spring, JPA nem Kafka. Frameworks e infraestrutura ficam nas bordas; a lógica de negócio fica isolada e testável sem container.

Decisões de projeto:
- **Observabilidade desde o início** — Prometheus, Grafana e Jaeger estão na infra antes do primeiro use case existir. Métricas e traces não são afterthought.
- **Logging estruturado 5W1H** — cada log emite JSON com `where`, `why`, `when`, `who`, `what`, `how` + `traceId`/`spanId` injetados automaticamente pelo Micrometer MDC. Facilita correlação em produção.
- **Event-driven preparado** — Kafka configurado com KRaft (sem Zookeeper), consumer/producer prontos no `application.yml`. Nenhum evento publicado ainda.

```
com.lmoraesdev.payment
├── adapter
│ ├── in.web ← PingController, GlobalExceptionHandler
│ └── out
│ ├── messaging ← (roadmap — Kafka producers)
│ └── persistence ← (roadmap — JPA repositories)
│ ├── in.web ← ChargeController, GlobalExceptionHandler, DTOs
│ └── out.persistence ← ChargeJpaEntity, ChargeMapper, ChargeRepositoryAdapter
├── application
│ ├── port.in ← (roadmap — interfaces de entrada)
│ ├── port.out ← (roadmap — interfaces de saída)
│ └── usecase ← (roadmap — casos de uso)
│ ├── port.in ← CreateCharge (interface), CreateChargeCommand, CreateChargeResult
│ ├── port.out ← ChargeRepository (interface)
│ └── usecase ← CreateChargeService
├── config
│ └── logging ← Log5w1h, Logger5w1hBuilder
│ ├── logging ← Log5w1h, Logger5w1hBuilder (structured 5W1H logging)
│ └── OpenApiConfig ← SpringDoc / Swagger UI
└── domain
├── event ← (roadmap — domain events)
├── exception ← DomainException (base abstrata com código de erro)
└── model ← (roadmap — entidades e value objects)
├── exception ← DomainException (base), InvalidAmountException
└── model ← Charge, Money, ChargeStatus
```

Especificação do domínio planejado (Charge Pix, máquina de estados, Money, idempotência): [`docs/architecture.md`](docs/architecture.md).
Decisões de projeto:
- **Domínio puro** — `Charge`, `Money`, `ChargeStatus` sem nenhuma anotação de framework
- **Armazenamento monetário em centavos** — `amount_centavos BIGINT` no banco; `Money` normaliza para scale=2 no domínio; o mapper converte nos dois sentidos. Elimina risco de ponto flutuante em operações financeiras.
- **Erros tipados** — `InvalidAmountException extends DomainException` → 422; genéricos → 500
- **Logging estratégico** — só o use case loga o evento de negócio (`charge_created`); controller e adapters não logam (OTel/Jaeger cobre o fluxo)
- **Problem Details (RFC 9457)** — todos os erros retornam `ProblemDetail` com `traceId`
- **Observabilidade desde o início** — Prometheus, Grafana e Jaeger na infra antes do primeiro use case

## Como rodar

**Pré-requisitos:** Docker Desktop com WSL2 integration habilitada; contexto Docker configurado para `default` (`docker context use default`).
**Pré-requisitos:** Docker Desktop com WSL2 integration habilitada; contexto Docker configurado para `default`.

```bash
# 1. Variáveis de ambiente
cp .env.example .env

# 2. Subir infra + app (sem Redis)
# 2. Subir infra + app
make up
# equivalente: docker compose up -d --build

# 3. Subir com Redis (profile cache)
docker compose --profile cache up -d --build
```

## API

### Criar cobrança

```http
POST /charges
Content-Type: application/json

{"amount": 150.00}
```

**201 Created**
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "ACTIVE",
"amount": 150.00,
"createdAt": "2025-06-05T18:00:00Z"
}
```

# 4. Verificar status
docker compose ps
**400 Bad Request** (amount inválido)
```json
{
"status": 400,
"title": "Validation failed",
"detail": "Um ou mais campos são inválidos",
"errors": { "amount": "must be greater than 0" },
"traceId": "abc123..."
}
```

## Endpoints e observabilidade

| URL | O que se vê |
|---|---|
| `http://localhost:8080/swagger-ui.html` | Swagger UI — documentação interativa da API |
| `http://localhost:8080/v3/api-docs` | Spec OpenAPI 3 em JSON |
| `http://localhost:8080/ping` | `{"status":"pong"}` — smoke test |
| `http://localhost:8080/actuator/health` | Status do app, banco e dependências |
| `http://localhost:8080/actuator/prometheus` | Métricas no formato Prometheus |
| `http://localhost:8090` | Kafka UI — tópicos, consumer groups, mensagens |
| `http://localhost:9090` | Prometheus — séries temporais, targets ativos |
| `http://localhost:3000` | Grafana — dashboard "Payment Overview" (admin/admin) |
| `http://localhost:3000` | Grafana — dashboard "Payment Overview" (credenciais do `.env`) |
| `http://localhost:16686` | Jaeger — traces distribuídos por operação |
| `http://localhost:8090` | Kafka UI — tópicos, consumer groups, mensagens |

## Testes

```bash
# Unitários — sem Docker, rápido
make test
# equivalente: ./mvnw test
# Unitários — sem Docker, rápido (~2s)
./mvnw test

# Integração — sobe PostgreSQL 18 via Testcontainers
make verify
# equivalente: ./mvnw verify
# Integração + cobertura — sobe PostgreSQL 18 via Testcontainers
./mvnw verify
```

| Teste | Tipo | O que cobre |
|---|---|---|
| `MoneyTest` | Unit | Validação de amount (7 casos table-driven) |
| `ChargeTest` | Unit | `create()`, `restore()`, `equals/hashCode` |
| `CreateChargeServiceTest` | Unit | Sucesso (3 valores) + erros de validação |
| `ChargeRepositoryIT` | Integration | Round-trip save/findById com PostgreSQL 18 real |
| `ChargeControllerIT` | Integration | POST 201, POST 400 Problem Details |

Convenção de nomes:
- `*Test.java` — testes unitários, executados pelo Surefire
- `*IT.java` — testes de integração, executados pelo Failsafe
- `*Test.java` — unitários, Surefire
- `*IT.java` — integração, Failsafe + Testcontainers

O teste `PaymentApiApplicationIT` valida que o contexto Spring sobe corretamente contra um banco PostgreSQL real, sem mocks.
Relatório JaCoCo gerado em `target/site/jacoco/index.html` após `./mvnw verify`.

## CI/CD

| Trigger | Job | O que roda |
|---|---|---|
| Push para `develop` ou `main` | Lint + Unit Tests | `spotless:check` → `checkstyle:check` → `mvnw test` |
| Push para `epic/**`, `develop` ou `main` | Lint + Unit Tests | `spotless:check` → `checkstyle:check` → `mvnw test` |
| Push para `main` ou PR → `main` | Full Verify + Docker Build | `mvnw verify` (unit + integração) → `docker build` |

O job de integração roda apenas no caminho para `main`, mantendo o ciclo de feedback rápido no `develop`.

## Padrões

**Formatação e estilo:**

```bash
./mvnw spotless:apply # formata (Google Java Format, AOSP 4-space)
./mvnw spotless:check # verifica (roda no CI)
./mvnw checkstyle:check # estilo (roda no CI)
```

**Git hooks** (ativar uma vez por clone):

**Git hooks** — shell scripts em `.githooks/` (ativar uma vez por clone):
```bash
git config core.hooksPath .githooks
```
Expand All @@ -134,13 +170,14 @@ git config core.hooksPath .githooks
| `commit-msg` | Valida formato Conventional Commits |
| `pre-push` | Executa `./mvnw verify` antes de subir |

Formato de commit: `tipo(escopo): descrição` — tipos aceitos: `feat fix docs style refactor test chore build ci perf revert`.
Formato de commit: `tipo(escopo): descrição` — tipos: `feat fix docs style refactor test chore build ci perf revert`.

**Makefile:**
> **Nota:** os hooks são scripts shell nativos (`.githooks/`). Husky está previsto para substituí-los em versão futura.

**Makefile:**
```
make up # docker compose up -d --build
make down # docker compose down (mantém volumes)
make down # docker compose down
make clean # docker compose down -v (remove volumes)
make logs # docker compose logs -f app
make test # ./mvnw test
Expand All @@ -149,19 +186,6 @@ make format # ./mvnw spotless:apply
make db # psql no container postgres
```

## Roadmap

O que está especificado em [`docs/architecture.md`](docs/architecture.md) e ainda não implementado:

- [ ] Entidade `Charge` com value object `Money` e `ChargeStatus`
- [ ] Máquina de estados (`PENDING → ACTIVE → PAID / EXPIRED / CANCELLED`)
- [ ] Caso de uso `CreateCharge` com idempotência por header
- [ ] Adapter de persistência JPA (`ChargeRepository`)
- [ ] Publicação de eventos de domínio no Kafka (`ChargeCreated`, `ChargePaid`, etc.)
- [ ] Consumer para eventos externos (webhook/notificação)
- [ ] Uso do Redis para cache de idempotência e locks
- [ ] Endpoints REST de cobrança (`POST /charges`, `GET /charges/{id}`)

---

[Leandro Moraes](https://github.com/lmoraesdev)
Loading
Loading