PT

Nº 06of 22Featured

Transactional e-commerce (under NDA)

Transactional e-commerce in Spring Boot: outbox with exponential retry, webhook idempotency, and rate limiting in Redis behind a circuit breaker

Year
2026
Status
IN PROGRESS
Role
Solo · under contract
Stack
Java 21 · Spring Boot · PostgreSQL · Redis +5
commerce-nda
TE

Java 21

No public screen — generated cover

01The project

A Brazilian brand's own store — name under contract — as a Java 21 / Spring Boot 4.1 modular monolith with a Next.js storefront. The system moves money, so most of the engineering is about the unhappy path: payment webhooks validated by signature and deduplicated against a processed-webhooks table; transactional emails in an outbox, claimed in a short transaction and sent outside any lock; retries with exponential backoff and an idempotency key at the provider; per-key rate limiting in Redis through an atomic Lua script that fails open when Redis goes down. 12 domain modules, Flyway migrations, and integration tests against real Postgres and Redis with Testcontainers.

02What was built

  1. Transactional outbox: claim in a short transaction, send without holding a lock, exponential backoff from 30s doubling to a 1h ceiling with jitter

  2. End-to-end idempotency: each webhook processed exactly once, an Idempotency-Key at the email provider

  3. Per-key rate limiting in Redis with an atomic Lua script that fails open, behind a circuit breaker that probes in half-open

  4. Payment webhooks validated by HMAC signature; the NF-e webhook by its own secret token under constant-time comparison — both fail closed

  5. 12 domain modules, Flyway, and integration tests with Testcontainers (real Postgres + Redis)

03Decisions

Decision 01

Question: Why an outbox instead of calling the email provider inside the transaction?

Answer: An HTTP call inside a transaction holds a pool connection while it waits on the network. The outbox breaks that into three steps: a short transaction that claims the row, a send with no lock at all, and a short transaction that records the result. The Idempotency-Key is the row id, so a resend after a crash is a no-op.

Decision 02

Question: Why does the rate limiter fail OPEN?

Answer: If Redis goes down, blocking all traffic takes checkout down with it — letting traffic through for a few minutes costs less than not selling. It logs ERROR so the failure raises an alert, and the circuit breaker keeps every request from paying the connection timeout while Redis is out.

Decision 03

Question: Why a modular monolith and not microservices?

Answer: Solo project, ~100 orders/month. Microservices here would buy network latency and deployment complexity without solving a single problem I actually have. The modules have clear boundaries for the day splitting them pays off.

04Stack

  • Java 21
  • Spring Boot
  • PostgreSQL
  • Redis
  • Next.js
  • TypeScript
  • Testcontainers
  • Flyway
  • Docker
Next project →Nº 07

Real-time monitoring: FastAPI backend metrics streamed over WebSocket and rendered in 3D