Advanced · Architect
Design a Production-Grade Capstone
Bring contracts, consistency, security, observability, testing, resilience, and delivery into one reviewable slice.
Primary source: Spring Framework 6.2 Core Technologies: The IoC Container
Lesson 3 of 3
1d streakThe Architectural Question
Portfolio pieces showcase happy paths; senior reviews probe failure drills, decision rationale, and whether another engineer can reproduce the evidence.
What this guide proves
- Document decisions and rejected alternatives
- Exercise failure and recovery paths
- Ship evidence that another engineer can reproduce
The Enterprise Capstone: Production-Grade Hardened Service
This capstone synthesizes every architectural discipline learned across the curriculum into a single, hardened enterprise service. A production microservice is not just controllers and entities; it is a resilient distributed system component.
The unified architecture incorporates:
1. Zero-Trust Security: Stateless JWT token authentication with role-based method security and strict CORS configuration.
2. Hexagonal Domain Isolation: Application-owned ports decoupling pure domain models from Postgres persistence and external REST APIs.
3. Fail-Open Distributed Caching: Redis cache-aside with Jackson 2 JSON serialization, TTL jittering, and custom CacheErrorHandler.
4. Resilient HTTP Integrations: Pooled RestClient instances wrapped with Resilience4j circuit breakers and exponential backoff retries.
5. Full Observability: Micrometer metrics, OpenTelemetry distributed tracing spans, and Kubernetes readiness/liveness state probes.
Distributed Consistency: The Transactional Outbox Pattern
In a microservices architecture, a single business action often requires updating a local relational database AND publishing an event to a message broker (e.g. Kafka or RabbitMQ). Dual-writing directly to both systems is fundamentally flawed: if the broker publish fails or times out after the database commits, downstream services fall out of sync permanently.
The Transactional Outbox pattern guarantees at-least-once event delivery without two-phase distributed transactions (XA):
package com.example.outbox;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.UUID;
@Repository
public class TransactionalOutboxRepository {
private final JdbcClient jdbcClient;
private final ObjectMapper objectMapper;
public TransactionalOutboxRepository(JdbcClient jdbcClient, ObjectMapper objectMapper) {
this.jdbcClient = jdbcClient;
this.objectMapper = objectMapper;
}
// Must participate in the SAME local database transaction as the business entity update!
@Transactional
public void publishEvent(String aggregateType, String aggregateId, String eventType, Object payload) {
try {
String json = objectMapper.writeValueAsString(payload);
jdbcClient.sql("""
INSERT INTO outbox_events (id, aggregate_type, aggregate_id, event_type, payload, created_at, processed)
VALUES (:id, :aggregateType, :aggregateId, :eventType, :payload::jsonb, :createdAt, false)
""")
.param("id", UUID.randomUUID())
.param("aggregateType", aggregateType)
.param("aggregateId", aggregateId)
.param("eventType", eventType)
.param("payload", json)
.param("createdAt", Instant.now())
.update();
} catch (Exception ex) {
throw new RuntimeException("Failed to serialize outbox event", ex);
}
}
}The Production Verification Checklist & SLO Validation
Before promoting any microservice to production, execute this comprehensive operational readiness verification:
| Verification Area | Target Production SLA | Verification Method |
|---|---|---|
| p99 Latency | < 50ms for cached reads, < 150ms for writes | Load test with hey at 1000 requests/second |
| Graceful Shutdown | 0 dropped in-flight requests during pod scale-down | Trigger rolling restart during live traffic test |
| Fail-Open Caching | HTTP 200 OK maintained during Redis outage | Pause Redis container during simulated load |
| Secret Sanitization | 0 plain-text passwords in logs, dumps, or env | Actuator /env endpoint audit |
| Zero-Trust Security | 401/403 returned on forged JWT or missing claims | Automated security integration test suite |
End-to-End Capstone Verification Walkthrough
Execute the complete end-to-end verification script to prove all architectural guarantees hold simultaneously:
# 1. Boot up application stack via docker-compose
docker compose up -d postgres redis
# 2. Run database migration and test suite
./mvnw clean verify
# 3. Verify health probes
curl -s http://localhost:8080/actuator/health | jq .status
# EXPECTED: "UP"
# 4. Create and process order through hexagonal use case
curl -i -X POST http://localhost:8080/api/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"customerId":"CUST-101","items":[{"sku":"SKU-1","quantity":2}]}'
# HTTP/1.1 201 Created
# 5. Verify atomic outbox event persisted in PostgreSQL
psql -d orders_db -c "SELECT aggregate_id, event_type, processed FROM outbox_events;"
# aggregate_id | event_type | processed
# -------------+-------------+-----------
# ORD-1001 | ORDER_PLACED| fActive Recall Checkpoint
Why does the Transactional Outbox pattern eliminate dual-write distributed transaction bugs?
Hint: Consider what happens when a database commit succeeds but the message broker network connection drops.
Show answer & explanation
By storing the outbound event in an outbox table inside the same local relational transaction as the business entity, either both the entity and the event save atomically, or both roll back together.
Working Implementation
Code Walkthrough
Present the checkout slice with an ADR set, runbook, threat model, and live failure drill.
package com.example.capstone;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.UUID;
@Service
public class ProductionMasteryCapstoneLab {
private final JdbcClient jdbcClient;
public ProductionMasteryCapstoneLab(JdbcClient jdbcClient) {
this.jdbcClient = jdbcClient;
}
public record OrderRecord(String orderId, String customerId, int totalCents, String status) {}
// Atomic outbox write participating in local relational transaction
@Transactional
public OrderRecord createOrderWithOutbox(String customerId, int totalCents) {
String orderId = "ORD-" + UUID.randomUUID().toString().substring(0, 8);
var order = new OrderRecord(orderId, customerId, totalCents, "CREATED");
// 1. Insert order record
jdbcClient.sql("INSERT INTO orders (id, customer_id, total_cents, status) VALUES (?, ?, ?, ?)")
.params(order.orderId(), order.customerId(), order.totalCents(), order.status())
.update();
// 2. Insert transactional outbox event atomically in same transaction
jdbcClient.sql("""
INSERT INTO outbox_events (id, aggregate_type, aggregate_id, event_type, payload, created_at)
VALUES (?, 'ORDER', ?, 'ORDER_CREATED', ?::jsonb, ?)
""")
.params(UUID.randomUUID(), orderId, "{\"totalCents\":" + totalCents + "}", Instant.now())
.update();
return order;
}
@Cacheable(cacheNames = "orders", key = "#orderId")
public OrderRecord findCachedOrder(String orderId) {
return jdbcClient.sql("SELECT id, customer_id, total_cents, status FROM orders WHERE id = ?")
.param(orderId)
.query((rs, rowNum) -> new OrderRecord(
rs.getString("id"),
rs.getString("customer_id"),
rs.getInt("total_cents"),
rs.getString("status")
))
.single();
}
}Synthesis & Production Verification
Deliberate Failure Condition
- Break: Ship a capstone without ADRs or a failure drill
- Observe: The defense stalls on "why did you choose this" and every recovery claim stays unverified
- Repair: Decision records, a runbook, a threat model, and a live drill are what turn a demo into defensible engineering evidence.
Production Acceptance Criteria
- Document decisions with rejected alternatives in an ADR set
- Exercise failure and recovery paths live, not on slides
- Package evidence another engineer can reproduce from scratch
Key Takeaway
A hardened enterprise microservice unites hexagonal domain isolation, fail-open caching, zero-trust security, and the Transactional Outbox pattern for distributed consistency.
Architectural & Technical Review
Core Interview Questions
Q01How does the Transactional Outbox pattern resolve the distributed dual-write consistency problem?Show answer
By storing the outbound domain event in an outbox table within the same local database transaction as the business entity, either both save atomically or both roll back, eliminating dual-write race conditions.
Q02Which core pillars constitute a production-hardened Spring Boot microservice architecture?Show answer
Zero-trust security (stateless JWTs), hexagonal domain isolation, cache-aside caching with TTL jitter and bounded fallbacks, pooled resilient HTTP clients with circuit breakers, and comprehensive OpenTelemetry tracing.
Q03What golden verification criteria must pass before promoting an enterprise service to production?Show answer
P99 latency SLO (< 50ms cached, < 150ms write), graceful shutdown with zero dropped requests under rolling restart, fail-open behavior during Redis outage, and automated ArchUnit architecture compliance.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Publishing directly to message brokers inside a database transaction without an outbox table — Network drops or broker timeouts after database commit lead to lost events and permanent desynchronization.
- Pitfall: Neglecting fail-open caching and timeout budgets in inter-service microservice calls — Allows an upstream outage to cascade across the entire microservice graph.
Module Certification Quiz
Prove Module Mastery
Comprehensive question set covering all lessons in this module.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources