64 KiB
Production Capability Platform Design
- Date: 2026-07-26
- Status: Proposed for implementation approval
- Scope: architecture and staged implementation design only
- Baseline: Java 21, Spring Boot 4.0.0, Gradle multi-module Clean Architecture template
1. Executive decision
This repository should evolve from a collection of integration seams into an opt-in production capability platform.
The target is not to enable Redis, Kafka, MongoDB, sessions, CDC, and every transport in every application. The target is:
- a developer selects a capability and provider by typed configuration;
- the composition root validates the selected topology and guarantees at startup;
- application code depends only on semantic, framework-free ports;
- the selected adapter supplies a real client, bounded defaults, health, metrics, failure semantics, and reusable contract tests;
- unused capabilities create no connection, background worker, schema, or implicit runtime behavior;
- advanced strategies remain available without pretending that one strategy is correct for every domain.
The default template remains light. Production capability packs are available by default but inactive by default. A capability is not called production-ready merely because a class or client seam exists.
Primary decisions
- Keep
domain-core,application-core, andshared-contractfree of Spring, Redis, Kafka, persistence, transport, and observability SDK types. - Put use-case-owned semantic ports and application policies in
application-core; put skeleton-wide transport/operational contracts inshared-contract; put provider selection and Spring composition inapp-bootstrap. - Initially preserve the registered 19-leaf topology. Expand the existing technology leaves in cohesive packages and split a leaf only when inbound/outbound direction or independent lifecycle requires it.
- Treat
adapter:outbound:cache-redisas the first Redis technology capability provider, but never reuse cache's fail-open behavior for sessions, idempotency, locks, or strict rate limits. - Add a future
adapter:inbound:messaging-kafkaleaf before implementing Kafka consumers. A consumer is a driving adapter and does not belong in the existing outbound producer leaf. - Keep an outbox append operation in the same source-of-truth datastore transaction as the business write. Make the dispatch mechanism selectable: polling or CDC.
- Define end-to-end messaging as at-least-once delivery plus idempotent consumers/inbox. Do not advertise generic exactly-once delivery across a database and broker.
- Separate evictable cache data from correctness-sensitive Redis data at the Redis deployment or cluster level, not only by key prefix or database number.
- Provide capability-level safe operations and versioned Lua scripts. Do not expose a general
RedisTemplate, Kafka producer, HTTP client, or cloud SDK to use cases.
2. Scope and non-goals
This design covers:
- Redis cache, rate limiting, sessions, idempotency, locks, and reusable atomic operations;
- polling and CDC outbox, Kafka producer/consumer, inbox, delivery semantics, and query/read models;
- file server, object storage, HTTP client, notification, JPA, MongoDB, web, GraphQL, gRPC, and WebSocket production baselines;
- provider selection, typed settings, health, observability, Gradle ownership, and test strategy;
- a phased path from the current skeleton to an operational baseline.
This design intentionally does not:
- select one infrastructure topology for every future product;
- invent domain-specific throughput or latency numbers;
- claim benchmark improvements without a real workload and environment;
- make every optional dependency active in the default application;
- promise cross-store atomicity, generic exactly-once processing, or strong consistency from a Redis lock;
- place example business concepts in production modules;
- create a universal repository, universal query DSL, or a raw infrastructure facade in
application-core.
3. Evidence-based current state
The repository already has stronger boundaries than a typical starter, but many adapters stop at an extension seam.
| Capability | Current evidence | Current operational gap |
|---|---|---|
| Redis cache | CacheStore exposes only get/put(String) and RedisClient is project-supplied |
No Redis SDK, TTL, delete, CAS, bulk operations, serialization policy, real health, or integration test |
| Rate limit | RateLimitAlgorithm has only FIXED_WINDOW; FixedWindowRateLimiter is in-process |
Not sliding-window as previously assumed; not multi-node; key map has no removal policy |
| Session | SecurityConfig fixes JWT, CSRF disabled, and STATELESS |
No Redis session repository, stateful security profile, rotation, shared logout, or multi-pod contract |
| Idempotency | Framework-free executor/port plus JPA implementation | Port documentation is DB-specific; no owner token; execution lease and replay TTL are conflated; Redis provider absent |
| Lock | Framework-free lock port plus local/JDBC LockRegistry providers |
Multi-node means JDBC only; no owner token, renewal, lease-lost signal, fencing, or Redis provider |
| Outbox | JPA/PostgreSQL polling relay with claim, retry, FIFO, and metrics | PostgreSQL SKIP LOCKED and mutable status row are coupled to polling; no CDC mode or immutable CDC envelope |
| Kafka | KafkaSender is a project-supplied seam and the module has no Kafka SDK |
No broker acknowledgement, producer security/tuning, consumers, inbox, retries, DLT, schema contract, or rebalance handling |
| HTTP client | Connect/read timeout, a globalCallTimeout label, retry, circuit breaker, buffered size bound, and diagnostics exist |
The global value neither bounds nor cancels an active call; no explicit bulkhead, per-client pool controls, SSRF policy, TLS/mTLS profile, redirect policy, or OTel-owned propagation |
| Object storage | Actual filesystem and synchronous S3/MinIO adapters exist | Entire object is byte[]; unsafe local-oriented defaults; no multipart, presigned operation, checksum contract, encryption, lifecycle, or orphan cleanup |
| File server | CSV is built and written with JDK filesystem APIs | Whole export is buffered; overwrite is non-atomic; no fsync/rename protocol, quota, retention, symlink defense, or shared-filesystem semantics |
| Notification | Route/fan-out/fail-open framework plus Google/Slack client seams | No real provider SDK, durable delivery, template/versioning, preference, dedupe, receipt, fallback, or provider rate control |
| MongoDB | Opt-in Spring Data Mongo configuration | No production document/port contract kit, concern policy, index/migration contract, replica-set transaction test, or change-stream checkpoint |
| GraphQL | Minimal schema/controller and error resolver | No depth/complexity policy, persisted queries, DataLoader baseline, field authorization, production schema checks, or subscription policy |
| gRPC | Netty server lifecycle, health, reflection, and error mapping | No feature proto build convention, TLS/mTLS, auth, deadline enforcement, retry contract, message limits, or stream backpressure baseline |
| WebSocket | In-process domain-event to STOMP simple-broker bridge | Not cluster-safe or durable; no broker relay profile, destination auth, bounded queues, reconnect/resume, or explicit drop policy |
| Observability | Structured logging, Micrometer, OTel bridge, actuator, and selected metrics exist | Coverage is uneven; several registry entries are not wired; manual traceparent propagation competes with real instrumentation |
| Bootstrap composition | The default app-bootstrap graph omits file server, object storage, MongoDB, GraphQL, gRPC, and WebSocket leaves |
Their source presence is not a runtime guarantee; blindly adding all dependencies would also activate unsafe defaults |
| Settings contract | YAML, environment-key registry, typed settings, and conditional beans are not consistently end-to-end aligned | Notification provider keys diverge, and JWT exposes JWKS/clock-skew settings while the implementation uses issuer discovery and a fixed skew |
Important correctness findings:
IdempotencyStorePort.complete(scope)anddiscard(scope)cannot distinguish an expired original owner from a new owner. A stale caller can overwrite or delete a reclaimed record.- The multi-instance startup validator checks bean names, not capability types or guarantees. Its
test accepts plain
Objectbeans, so it can report safety without a working distributed implementation. - The current outbox row is mutated through
PENDING/IN_FLIGHT/PUBLISHED/.... Debezium's standard Outbox Event Router expects the outbox event source to behave as an insert-only queue, so CDC cannot be attached to the current mutable design without a schema/behavior split. - The current authenticated rate-limit key lacks a route or policy dimension, while the unauthenticated key includes the route. This makes policy isolation inconsistent.
- Blanket
catch (Exception)cache fail-open behavior can hide codec or programming defects as ordinary cache misses. - Merely composing every existing leaf is unsafe: GraphQL and WebSocket lack a uniform module enable gate, gRPC defaults include plaintext/reflection behavior, and filesystem object storage can activate through a missing-property default.
- The HTTP decorator order currently lets the circuit breaker observe a complete retry bundle, while its documentation says each attempt is counted. The declared global call timeout also does not actively cancel an already-running attempt.
4. Alternatives considered
A. One large infrastructure starter in app-bootstrap
This is rejected. It would make classpath presence activate too much auto-configuration, blur provider ownership, and turn the composition root into an infrastructure implementation module.
B. A new Gradle leaf for every capability-provider pair
Examples would be cache-redis, lock-redis, session-redis, idempotency-redis, and
rate-limit-redis. This has the cleanest physical isolation but creates module and connection
configuration duplication immediately. It also changes the exact 19-leaf registry before the
semantic contracts are stable.
This becomes appropriate only when a provider has an independent release cadence, security boundary, deployment lifecycle, or dependency graph.
C. Semantic ports plus existing technology leaves
This is the selected first-stage design.
application-coreowns semantic ports and framework-free orchestration.- existing adapter leaves own actual SDKs and provider-specific behavior;
app-bootstrapselects exactly one provider per required capability;- provider packages use separate failure policies even when they share a client library;
- module splits are made only for direction or lifecycle reasons.
The known compromise is the cache-redis module name. Its short-term responsibility becomes Redis
technology capabilities, while a rename to adapter:outbound:redis is deferred to a separately
approved module-registry migration.
D. Extract an external platform BOM/starter repository now
This is deferred. Extraction before the contracts and test kits are proven would freeze immature APIs and make local architecture verification harder. A later extraction may publish provider artifacts and a BOM after at least two real consumers validate the contracts.
5. Target architecture
flowchart LR
WEB[Web / GraphQL / gRPC / WebSocket] --> APP[application-core use cases]
KIN[Future inbound Kafka adapter] --> APP
APP --> PORTS[Semantic outbound ports]
PORTS --> JPA[JPA/PostgreSQL providers]
PORTS --> MONGO[Mongo providers]
PORTS --> REDIS[Redis capability providers]
PORTS --> MSG[Messaging producer providers]
PORTS --> IO[HTTP / notification / storage / files]
BOOT[app-bootstrap composition root] -. selects and validates .-> WEB
BOOT -. selects and validates .-> JPA
BOOT -. selects and validates .-> MONGO
BOOT -. selects and validates .-> REDIS
BOOT -. selects and validates .-> MSG
OBS[Metrics / traces / logs / health] -. decorates runtime edges .-> BOOT
Layer ownership
| Concern | Owner | Must not leak |
|---|---|---|
| Domain invariant, domain event | domain-core |
Spring, SDK, transport, persistence |
| Application-invoked cache/idempotency/lock/inbox semantic contract | application-core |
Redis, SQL, Kafka, Servlet, Micrometer |
| Feature-specific query shape | feature application package via *QueryPort |
JPA entity, web DTO, generic repository |
| Transport-edge rate limit and operational descriptor contracts shared across leaves | shared-contract |
provider SDK, Servlet, Redis, and business/domain concepts |
| Redis connection, codec, scripts, cache/rate/idempotency/lock provider | adapter:outbound:cache-redis initially |
use-case and business policy |
| Session repository implementation | Redis provider package; security behavior composed by web/bootstrap | Spring Session types in application/domain |
| JPA transaction, same-store outbox/inbox/idempotency, DB lock | adapter:outbound:persistence-jpa |
use-case policy |
| Kafka producer | adapter:outbound:messaging |
consumer handler and use case |
| Kafka consumer | future adapter:inbound:messaging-kafka |
producer implementation and persistence entity |
| HTTP/security/response mapping | relevant inbound adapter | repository/client SDK |
| Provider selection, exact-one validation, health composition | app-bootstrap |
business policy |
New or revised core contracts
The implementation plan should introduce or revise these contracts:
CachePort,CacheRegion,CacheKey,CacheLookup,CacheWritePolicy,CacheAsideExecutor;- transport-edge
EdgeRateLimitContract,RateLimitRequest, andRateLimitDecisioninshared-contract; a use-case-ownedBusinessQuotaPortbelongs inapplication-coreonly when a domain/application policy actually invokes it; - owner-token-based
IdempotencyStorePortclaim/renew/complete/release; - existing
DistributedLockPortdocumented as an efficiency mutex; - separate
FencedLockPort, and laterLeaderElectionPort,SemaphorePort, orWorkClaimPortonly when those semantics are needed; InboxStorePortandMessageConsumptionExecutor;- richer outbox/integration-message envelope;
ReadConsistencyand versioned cursor values used by feature-specific query ports;- streaming and precondition-oriented object/file contracts.
These are deliberately not application ports:
- a raw Redis command API;
RedisTemplate, Lettuce/Jedis connections, or Spring Session repositories;- a generic Kafka producer/consumer;
- a generic
RestClient; - a general cloud SDK facade;
- a generic repository or arbitrary query language.
Session is a transport/security concern. A SessionControlPort is added only if an application use
case must revoke a user's sessions, enforce a business-driven concurrent-session rule, or audit
session control.
6. Capability readiness model
Each capability has a visible readiness level:
| Level | Meaning | Required evidence |
|---|---|---|
| R0 Contract | Type, seam, or placeholder only | unit tests and architecture boundary |
| R1 Local | Works in one local process or local service | focused integration test and documented limitations |
| R2 Production baseline | Real provider, safe configuration, failure semantics, health, metrics, security, graceful lifecycle | provider contract, real-service integration, concurrency/failure tests, runbook |
| R3 Scale/HA | Cluster/failover/rolling-upgrade behavior is proven | topology tests, compatibility matrix, recovery and capacity runbooks |
The current repository contains a mixture of R0, R1, and partial R2 components. Documentation and startup diagnostics must not label an R0 seam as an R2 provider.
Every capability receives a "capability card" containing:
- owner module and semantic port;
- provider IDs and readiness level;
- guarantee and explicit non-guarantees;
- default failure mode and allowed overrides;
- required topology and persistence/eviction policy;
- configuration and secrets;
- liveness/readiness impact;
- bounded-cardinality metrics and trace spans;
- fit/non-fit guidance, cost model, and resource bounds;
- common unsafe recipes, the race/failure they create, and the safe replacement;
- focused, integration, and failure-test commands;
- rolling-upgrade and recovery notes;
- runbook links.
This is how the template teaches operational depth without forcing every project to use every feature. R2 examples are executable contract examples, not copy-only snippets: a provider must prove its documented concurrency and failure semantics before the card can claim the guarantee.
7. Provider selection and configuration
Provider selection becomes explicit per capability. multi-instance-enabled remains descriptive
runtime context, not an implicit provider selector.
provider: disabled | <provider-id> is the activation SSOT for a selectable capability. A binding
map uses disabled | <provider-id> per binding, and a mode capability such as outbox uses
dispatch-mode: disabled | polling | cdc. A leaf-level enabled flag exists only when there is no
provider or mode axis. If a legacy flag is temporarily retained, any disagreement with the SSOT is
a startup error.
matchIfMissing=true, classpath presence, or a local-provider default must never activate a
production capability. Local, in-memory, plaintext, reflection, auto-create, and filesystem
profiles are rejected in a production profile unless a deployment explicitly opts into a
documented exception.
Illustrative target configuration:
ca-skeleton:
capabilities:
cache:
bindings:
worklog-summary: redis
rate-limit:
provider: redis
fallback: local-emergency
idempotency:
provider: jdbc
guarantee: same-store-transactional
lock:
provider: jdbc
guarantee: efficiency
outbox:
dispatch-mode: polling
messaging:
producer: kafka
consumer: disabled
security:
auth-mode: jwt
providers:
redis:
connections:
cache:
endpoint: ${REDIS_CACHE_ENDPOINT}
coordination:
endpoint: ${REDIS_COORDINATION_ENDPOINT}
session:
endpoint: ${REDIS_SESSION_ENDPOINT}
Credentials, keys, and certificates are secret references or environment-provided values, not literal repository defaults.
Capability descriptors
Each active provider contributes a typed descriptor with at least:
capabilityId;providerId;- readiness level;
- guarantee class;
- failure mode;
- multi-instance support;
- required backing role;
- readiness impact;
- implementation version.
app-bootstrap validates descriptors and selected settings. It does not accept arbitrary beans
with a magic name.
Examples of fail-fast topology rules:
redis-sessionrequires the session Redis role, CSRF/cookie settings, and a session repository;idempotency=rediscannot claim same-store transactional atomicity for a JDBC business write;outbox=cdcand the polling scheduler cannot both be active;multi-instance=truerejects an in-process-only required lock or rate-limit provider unless an explicit degraded mode is selected;- a required Kafka producer must support broker acknowledgement and bounded delivery timeout;
- a correctness capability cannot use the evictable cache Redis role;
- no enabled provider may have an unregistered health, metrics, or configuration card.
Configuration contract tests traverse the complete path:
environment-key registry -> application YAML -> typed settings -> validation -> conditional bean
They fail on unknown/dead keys, missing registered keys, multiple active providers, inactive provider settings that unexpectedly create beans, or a selected provider without its SDK and health indicator.
Failure modes
Failure policy is capability-specific, not globally "fail open" or "fail closed".
| Capability | Default failure policy |
|---|---|
| Optional cache | Fail open to source load; surface degraded result and metric |
| Cache codec/programming error | Fail closed for the operation; evict/quarantine corrupt entry; do not hide as miss |
| Security session store | Fail closed; authentication state must not be invented |
| Keyed mutation idempotency | Fail closed |
| Strict rate limit for abuse/cost boundary | Fail closed or a deliberately bounded local emergency limiter |
| Availability-oriented rate limit | Explicit local emergency fallback, never unlimited silent bypass |
| Efficiency lock | Fail or continue only according to declared use-case policy |
| Fenced correctness lock | Abort protected work on acquisition or lease-loss failure |
| Outbox append | Roll back the business transaction |
| Outbox dispatcher outage | Keep writes accumulating; alert on lag/backlog |
| Best-effort notification/message | Explicit fail open |
| Durable notification/message | Outbox/inbox, retry, and terminal failure path |
8. Redis capability design
The Redis summary in this section is expanded and governed by Redis Production Capability Deep Design. That document fixes the application contracts, role/deployment isolation, key and codec schemas, versioned Function/Lua catalog, cache strategies, rate-limit algorithms, lease/fencing and idempotency state machines, session profile, client backpressure, security, observability, and topology/failure CI in implementation-ready detail.
8.1 Runtime role isolation
At minimum, Redis is modeled as three roles:
| Role | Data | Eviction/durability expectation | Typical failure semantics |
|---|---|---|---|
cache |
recomputable values, negative entries, soft locks | bounded memory, an allkeys-* policy selected by operations |
usually fail open |
coordination |
idempotency, locks, strict quotas, fencing counters | noeviction, HA, persistence/capacity alarms |
usually fail closed |
session |
authenticated sessions and indexes | noeviction, HA, serializer compatibility, persistence |
fail closed |
A Redis database number or key prefix does not isolate maxmemory, eviction, failover, or noisy
neighbors. Production settings require separate managed databases, clusters, or instances where
these guarantees differ.
Each role has typed endpoint/topology/TLS/ACL/timeout/pool/topology-refresh settings and its own
health component. Application code never issues CONFIG SET; deployment configuration owns
maxmemory, eviction, persistence, and replica policy.
8.2 Safe operation catalog
The Redis leaf provides reusable, capability-oriented operations:
| Category | Baseline operations |
|---|---|
| Cache | get, multi-get, put with bounded TTL, put-if-absent, evict, multi-evict, touch, namespace-generation bump |
| Atomic primitives | set-if-absent-with-TTL, compare-and-delete, compare-and-expire, increment-with-initial-TTL |
| Rate limit | fixed window, sliding counter, token bucket; sliding log and GCRA opt-in |
| Idempotency | atomic claim, renew, complete, release with owner token |
| Lock | acquire, owner-safe renew/release, optional atomic fencing counter |
| Streaming/invalidation | bounded Redis Streams or Pub/Sub helpers only inside messaging/cache adapters |
These are adapter utilities, not use-case APIs. Raw list/set/hash commands remain available to a new adapter implementation through the client library, but are not promoted as a stable application contract.
Every packaged script has a ScriptDescriptor:
- stable name and semantic version;
- source checksum;
- key count and cluster-slot rule;
- argument/result schema;
- complexity and maximum collection size;
- timeout/failure behavior;
- metrics name;
- compatible Redis versions.
Every public helper also has an operation contract that states atomicity scope, command/script complexity, worst-case state growth, cluster-slot constraints, clock source, retry safety, and failure result. Its documentation pairs the common unsafe multi-command recipe with the packaged atomic replacement, and its provider contract contains a concurrent race test. This makes the reason for Lua or another primitive visible to a developer instead of presenting a magic helper.
8.3 Why Redis single-threading does not remove races
An individual Redis command is serialized, but a client sequence such as:
GET -> decide -> INCR -> EXPIRE
is not one command. Other clients can interleave between its steps. The INCR may succeed while
EXPIRE is skipped after a client failure, or two clients can both make a decision from stale
state.
Lua scripts solve the read/decide/write atomicity problem by running as one atomic server-side operation. They introduce another risk: Redis blocks other work while a script runs. Therefore:
- scripts are O(1) or strictly bounded;
- no unbounded loop,
KEYS, large collection scan, network, filesystem, or dynamic code; - keys are declared through
KEYS[]; - multi-key scripts use a common, narrow hash tag such as a resource hash, not a whole-tenant hot slot;
- time arithmetic uses integer milliseconds and a documented clock source;
- scripts are classpath resources, not concatenated strings;
- the client uses cached execution (
EVALSHA) with safe reload onNOSCRIPT; - Redis Functions are an opt-in deployment mode only when function installation/version ownership is available;
- slow-script, command-timeout, pool, and server-latency signals are monitored.
8.4 Cache contract and strategies
The current String get/put API is replaced by an application-owned cache contract that separates:
HIT,MISS,NEGATIVE_HIT, andDEGRADED;- region and key from backend provider;
- payload schema/version from Redis serialization;
- positive TTL, negative TTL, soft TTL, and hard TTL;
- backend failure from codec/programming failure.
The Redis adapter stores an opaque, versioned payload. A framework-free CacheCodec<T> and
CacheAsideExecutor<T> keep domain/application values type-safe without allowing the Redis adapter
to serialize arbitrary domain objects by reflection. JDK native serialization is forbidden.
Baseline strategies:
- cache-aside;
- after-commit invalidation, with eviction preferred over blind cache update;
- bounded positive/negative TTL;
- TTL jitter;
- process-local single-flight;
- versioned key namespace;
- payload size limit;
- batched
SCANplusUNLINKfor operator cleanup, never regularKEYS; - low-cardinality hit/miss/error/load metrics.
Opt-in strategies:
- stale-while-revalidate with soft/hard TTL;
- refresh-ahead;
- probabilistic early refresh;
- L1 local plus L2 Redis;
- distributed stampede suppression with double-check and bounded soft lock;
- client-side tracking or Pub/Sub invalidation;
- compression above a configured threshold;
- generation-based mass invalidation.
Write-through cannot imply atomic DB+Redis commit. Write-behind is not an in-memory executor behind the cache API; it requires a durable outbox/stream consumer and its own retry/DLT semantics. Pub/Sub invalidation is best effort, so TTL and schema version remain the recovery boundary.
8.5 Rate-limit strategy registry
Transport adapters resolve principal, tenant, API key, IP, route, and policy, then invoke the
framework-neutral EdgeRateLimitContract from shared-contract. The Redis provider implements
that contract and app-bootstrap wires the edge; application-core is not involved unless a
separate business quota is part of a use case. The contract receives only an opaque hashed subject
and policy:
RateLimitRequest(policyId, subjectHash, cost)
-> RateLimitDecision(allowed, remaining, retryAfter, resetAt)
The stable key dimensions are:
environment + policyId + tenant? + subjectHash
Raw PII, token, request body, and full URL are forbidden in Redis keys, logs, traces, and metric tags.
| Algorithm | Operational characteristic | Availability |
|---|---|---|
| Fixed window | O(1), simple, permits boundary burst | local/R1 and Redis/R2 |
| Sliding-window log | exact but one sorted-set member per event; memory/CPU grows with volume | advanced opt-in |
| Sliding-window counter | approximate sliding window with bounded O(1) state | production option |
| Token bucket | independently controls average rate and burst capacity | recommended Redis default |
| Leaky bucket | smooth output; synchronous HTTP request queueing is not allowed | background/workflow opt-in |
| GCRA | precise smoothing with compact state | advanced opt-in |
Policies are selected by stable policyId, not a global algorithm. A policy declares algorithm,
capacity/rate/window, burst, cost, failure mode, and subject dimensions. Retry-After comes from
the decision rather than the current fixed one-second value.
When Redis fails, an optional emergency limiter is conservative, process-local, bounded in size and TTL, and emits a degraded signal. It is not described as globally accurate.
8.6 Session profile
Authentication modes are exclusive:
jwt | redis-session
JWT remains the default:
- stateless resource server;
- no session repository;
- CSRF may remain disabled for bearer-only APIs;
- Redis failure does not affect authentication.
redis-session is opt-in:
- real Spring Session Redis implementation on the separate session role;
IF_REQUIREDsession creation;- CSRF enabled and tested;
Secure,HttpOnly,SameSite, path/domain, expiry, and session-id rotation settings;- logout deletes the server session;
- explicit serializer with versioning and allowed types; no JDK serialization;
- rolling-deploy compatibility test;
- multi-pod read/touch/expiry/logout contract;
- fail-closed repository behavior and readiness inclusion;
- optional indexed repository only when principal lookup/concurrent-session control is required.
Login endpoints and identity proofing remain product decisions. The capability pack provides secure session storage and web-security composition, not a guessed login business flow.
8.7 Redis health and telemetry
Required bounded metrics include:
- cache get/load/invalidate outcome and duration by logical region;
- rate-limit decision/fallback by policy and algorithm;
- idempotency claim/replay/conflict/takeover/store operation;
- lock acquire/renew/lease-lost/release/fencing rejection;
- session repository operation/expiry/error;
- Redis command latency, timeout, connection pool saturation, reconnect, and server memory/eviction.
No cache key, user, tenant, session ID, message ID, or lock resource is a metric tag.
9. Idempotency design
The current find -> tryBegin and scope-only completion API becomes one atomic ownership protocol:
claim(request)
-> ACQUIRED(ownerToken, leaseUntil)
-> REPLAY(storedResponse, replayUntil)
-> IN_PROGRESS(retryAfter)
-> FINGERPRINT_MISMATCH
Follow-up operations:
renew(ownerToken, newLeaseUntil)
complete(ownerToken, response, replayUntil)
release(ownerToken)
Rules:
- execution lease and completed-response replay TTL are separate;
- completion and release compare the owner token;
- stale-owner operations return ownership-lost and cannot mutate a new claim;
- fingerprint canonicalization/version is explicit;
- stored response size, encryption/PII, codec version, and allowed replay metadata are bounded;
- mutations fail closed when the idempotency store is unavailable;
- JPA and Redis providers run the same contract suite;
- the sample module must demonstrate a POST genuinely invoking the executor rather than only accepting an unused header.
Guarantees are declared:
REQUEST_REPLAY: suppresses concurrent/repeated request execution as far as the store protocol can observe;SAME_STORE_TRANSACTIONAL: the idempotency record and business change commit in the same datastore transaction;EXTERNAL_IDEMPOTENCY: an outbound provider also receives a stable idempotency key.
A Redis claim plus a JDBC business write is not SAME_STORE_TRANSACTIONAL. A crash after the
business write and before Redis completion can cause replayed execution. DB constraints,
intrinsically idempotent commands, outbox, compensation, or downstream idempotency keys remain
necessary.
10. Lock and coordination design
One mutex interface should not impersonate every coordination primitive.
| Contract | Purpose | Correctness expectation |
|---|---|---|
DistributedLockPort / future DistributedMutexPort |
reduce duplicate work or contention | efficiency only; DB constraints/invariants remain authoritative |
FencedLockPort |
prevent stale holders from writing | protected resource must reject lower fencing tokens |
LeaderElectionPort |
select an active coordinator | explicit leadership/lease lifecycle |
SemaphorePort |
bound distributed concurrency | permit ownership and expiry |
WorkClaimPort |
claim queue/jobs | claim token and visibility timeout |
A Redis lease uses:
SET key ownerToken NX PX lease;- owner-checked renew and release Lua scripts;
- bounded acquisition retry with jitter;
- maximum hold duration and bounded renewal count;
- lease-lost signal;
- idempotent release;
- optional atomic monotonically increasing fencing counter in the same cluster slot.
Blind DEL is forbidden. An unlimited watchdog is forbidden. Fencing is useful only if the
database, object store, or downstream write port stores and rejects stale tokens.
The fencing counter is separate from the expiring lease and never expires or resets. The protected resource rejects a token lower than its accepted high watermark. Equality is accepted only for the same owner token and lease epoch; a different owner must present a strictly greater token. If Redis failover can lose an acknowledged increment, this provider cannot claim an R2 correctness guarantee: acquisition fails closed/unready until a monotonic epoch above the protected resource's recorded high watermark is established. A lock plus a best-effort Redis counter is still only an efficiency mechanism.
Provider guidance:
- local provider: development/single-node only;
- JDBC table/advisory provider: low-rate coordination close to the primary DB;
- Redis provider: low-latency coordination with explicit failover limitations;
- a consensus system may be added later for stronger lease/election requirements;
- Kafka partition ownership is a work-distribution mechanism, not a generic mutex.
Redis/Redlock is not advertised as a strong correctness guarantee. Network partitions, failover, lease expiry, pauses, and wall-clock behavior require fencing or an authoritative invariant.
11. Outbox, CDC, messaging, and inbox design
11.1 What can and cannot be provider-neutral
The outbox append must share the source-of-truth transaction:
- a JPA/PostgreSQL business write appends to PostgreSQL;
- a Mongo business write appends to Mongo in the same supported transaction;
- moving the append to Redis or Kafka would lose atomicity unless a real distributed transaction is introduced.
The dispatch strategy is selectable:
disabled | polling | cdc
11.2 Immutable event plus delivery state
Split the current mutable row:
outbox_event
immutable event envelope
outbox_delivery
polling-only claim and delivery state
outbox_event contains:
- event/message ID;
- event type and schema version;
- aggregate type, aggregate ID, and aggregate sequence/version;
- logical destination and partition key;
- content type and payload;
- occurred-at time;
- tenant when active;
- correlation and causation IDs;
- trace context allowlist.
Physical Kafka topic names are adapter configuration. Application event types do not become topic names by convention.
Polling mode writes both rows in the business transaction and mutates only outbox_delivery.
Polling retains current short claim transactions, broker publish outside the DB transaction,
retry/backoff, aggregate ordering, orphan reclaim, and dead-letter behavior, with these additions:
- claim owner token on every state transition;
- aggregate sequence rather than timestamp-only order;
- broker acknowledgement deadline;
- one combined retry budget across broker client and relay;
- operator replay/requeue/skip tooling and audit;
- explicit resolution for a dead event that blocks aggregate ordering.
CDC mode:
- writes only the immutable event row;
- does not create the Java polling scheduler or polling publisher;
- uses an externally deployed Kafka Connect/Debezium connector and Outbox Event Router;
- routes event ID to a header and aggregate/partition key to the broker key;
- owns connector predicate, schema mapping, offset, snapshot, WAL/replication-slot, retention, restart, and recovery configuration;
- monitors connector lag, retained WAL, offset progress, serialization errors, and restarts;
- does not reuse polling
PUBLISHEDstatus or polling lag metrics.
outbox_event is time/range partitioned for bounded retention. Cleanup may purge only a closed
partition whose high watermark is proven consumed by the connector checkpoint and whose replay
retention has elapsed. Cleanup never updates event rows, explicitly filters any cleanup
delete/tombstone records, alarms on table/partition growth, and is tested across connector outage,
restart, and snapshot cutover.
Polling and CDC dispatch are mutually exclusive. Switching modes requires a runbook covering write freeze or dual-read avoidance, backlog drain, connector offset verification, and rollback.
11.3 Kafka producer baseline
The messaging leaf gains a real client/provider:
- acknowledgement-aware send result;
acks=alland idempotent producer configuration;- bounded delivery timeout and retry budget;
- stable key/partition ordering;
- compression/batch limits;
- TLS/SASL and secret references;
- schema serializer and compatibility validation;
- low-cardinality metrics and OTel propagation;
- readiness for required producer paths;
- graceful flush and shutdown.
Kafka producer transactions are used only for Kafka-native workflows where their boundary applies, such as consume-process-produce with committed offsets. They do not make a database write and Kafka publish one atomic transaction.
Best-effort MessagePublisher remains explicitly named and documented as best effort. Durable
business events use outbox.
11.4 Kafka consumer and inbox
Before consumer implementation, add an adapter:inbound:messaging-kafka leaf through the registry
migration workflow. Its production baseline includes:
- manual acknowledgement after application success;
- handler/schema/version allowlist;
- bounded concurrency and queues;
- pause/resume backpressure;
- rebalance and
max.pollhandling; - bounded retry topic or delayed-retry strategy;
- poison/deserialization failure classification;
- DLT plus audited replay tooling;
- trace context restoration;
- graceful drain and shutdown;
- consumer lag/rebalance/retry/DLT metrics.
InboxStorePort scope is:
consumerGroup + handlerName + tenant? + messageId
For a handler that writes a database, inbox claim/completion and the business write commit in the same database transaction. A Redis inbox may be a fast prefilter or serve a DB-free handler, but it cannot claim same-store atomicity for a JDBC or Mongo write.
End-to-end wording is:
at-least-once delivery + idempotent consumer/inbox
Redis Streams may be offered later as a smaller-scale messaging provider with consumer-group, pending-entry, reclaim, trim, and dedupe contracts. It is not treated as a drop-in Kafka clone.
12. Query and persistence design
12.1 Query progression
Keep feature-specific *QueryPort interfaces. Do not add one universal QueryPort<Q, R> or generic
repository.
Supported progression:
- same-store aggregate read;
- same-store projection via JPQL/JdbcTemplate/Mongo projection;
- primary/read-replica routing;
- separate read model populated through Kafka/CDC;
- purpose-specific search or analytical store.
Common application values:
- opaque, signed, versioned cursor;
- bounded page size;
- allowlisted sort/filter;
ReadConsistencysuch asSTRONG,READ_YOUR_WRITES,BOUNDED_STALENESS,EVENTUAL;- projection checkpoint and lag.
TransactionPort.inRead() does not silently mean "use a replica." The query's consistency policy
and request context select primary or replica. Read-after-write flows remain on primary unless a
causal/checkpoint contract proves otherwise.
12.2 JPA/PostgreSQL production baseline
Preserve:
- OSIV disabled;
- application-owned transaction port;
- Flyway migrations;
- persistence exception translation;
- current polling outbox, idempotency, and JDBC lock providers as selectable providers.
Add:
- explicit pool sizing, acquisition timeout, leak detection policy, and shutdown;
- statement/query/lock timeout hierarchy within the request deadline;
- batch write and fetch-size settings;
- N+1 detection and representative query-plan tests;
- optimistic version and bounded pessimistic-lock use;
- primary/read-replica routing with explicit consistency;
- migration expand/contract and rollback/roll-forward rules;
- tenant filter/index/unique-constraint rules when tenancy is active;
- slow query and pool saturation metrics;
- same-store inbox implementation;
- provider packages that make PostgreSQL-specific SQL visible and tested.
Database-backed outbox/idempotency/lock remain valid providers. They stop being the only providers.
12.3 MongoDB production baseline
The Mongo leaf remains free of example business documents and gains reusable infrastructure:
- typed URI/topology/TLS/credential/timeout/pool settings;
- explicit read preference, read concern, write concern, and transaction options;
- replica-set/sharded-cluster requirement validation for transactions/change streams;
- index manifest, unique/TTL indexes, drift detection, and migration runner;
- schema validation and optimistic versioning guidance;
- bounded query/page/time limits;
- retryable read/write classification;
- change-stream resume token/checkpoint store and oplog-window monitoring;
- same-store Mongo outbox/inbox option;
- real replica-set Testcontainers contract;
- rolling serializer/schema compatibility.
Change streams are resumable only while the required oplog history and compatible pipeline/options remain available. Pool sizing accounts for long-lived change-stream cursors.
13. Remaining outbound capability baselines
13.1 HTTP client
The implementation-level authority for this capability is HTTP Client Production Capability Deep Design. This subsection remains the cross-capability baseline; where detail differs, the dedicated design governs.
Preserve the current connect/read timeout intent, bounded-response intent, retry/circuit-breaker
seams, shutdown guard, and diagnostics as characterization inputs, not as proven guarantees. The
dedicated audit shows that the current globalCallTimeout only gates whether another retry may
start; it does not actively bound or cancel DNS, pool wait, connect, TLS, write, response body, or
backoff. It also shows that the documented decorator order differs from the code. If every physical
attempt must affect circuit-breaker state, retry repeats a circuit-breaker-wrapped attempt; the
logical-call deadline and concurrency bulkhead remain outside that loop. If a provider intentionally
measures one logical call instead, that is a different named policy and test suite, not an accidental
wrapper-order side effect.
Add:
- named client registry with per-dependency settings;
- explicit connection pool total/per-route limits, acquisition timeout, idle eviction, DNS policy, and graceful close;
- bulkhead and optional outbound rate limit;
- retry only for declared safe/idempotent operations, with exponential jitter and
Retry-Afterhandling; - a single total deadline covering pool wait, attempts, backoff, and body read, with active cancellation of the engine call and response stream when the budget expires;
- redirect disabled by default or host-allowlisted;
- scheme/host/port/CIDR allowlist and DNS rebinding/SSRF defense;
- TLS trust, hostname verification, mTLS, proxy, and certificate rotation;
- request/response header and body-size allowlists;
- upload/download streaming and cancellation;
- OTel instrumentation owns trace propagation. Remove the manual
traceparentwriter when the real tracer is active; - failure injection and pool-exhaustion tests.
Use cases continue to depend on feature-specific anti-corruption ports such as RepoStatsPort, not
on OutboundHttpClient.
13.2 Notification
Split technical routing from business consent/preferences.
Application intent contains:
- channel;
- logical template ID and version;
- locale;
- recipient reference/address;
- typed template parameters;
- delivery mode and idempotency key;
- correlation/tenant context.
Application/domain policy owns consent, preference, and quiet-hour decisions when those are business rules. The adapter owns:
- real provider clients;
- template rendering/versioning/localization;
- priority/fallback/fan-out routing;
- provider quotas and bounded retry;
- dedupe and provider idempotency key;
- durable mode through outbox/message;
- webhook signature verification and delivery receipts through an inbound adapter;
- bounce/suppression handling;
- PII-safe logs, encrypted queue content, and retention;
- per-provider health and delivery outcome metrics.
Critical notification is never routed through the current unconditional fail-open path. Best-effort and durable interfaces are explicit.
13.3 Object storage
Replace whole-object byte[] as the only path with:
- streaming upload/download and range reads;
- metadata/head contract;
- checksum algorithm/value contract and verification;
- conditional create/update/delete using version/ETag preconditions;
- presigned upload/download request with bounded expiry, content type, and size;
- multipart start/upload/complete/abort and orphan cleanup;
- server-side encryption and KMS settings;
- TLS/endpoint/region/credential-chain validation;
- lifecycle/versioning/retention policy checks;
- quarantine/malware-scan hook before publish;
- payload and metadata limits;
- metrics, tracing, and retry classification.
Local filesystem and S3/MinIO pass the same semantic contract where the backend can support it. Provider-specific optional capabilities are reported explicitly rather than silently emulated.
Production defaults do not point to local MinIO, auto-create buckets, use static credentials, or return internal filesystem paths to clients.
Database state and object storage cannot share one local transaction. Workflows such as an image attachment therefore use an explicit staged lifecycle:
stage upload -> verify checksum/scan -> commit attachment metadata -> finalize visibility
Failure paths use idempotent compensation plus an orphan reconciler with retention and audit evidence. A use case must not perform an irreversible object write inside a database transaction and assume rollback covers both systems.
13.4 File server
The authoritative implementation-level design for this capability is Fileserver Production Capability Deep Design. This subsection is only the cross-capability baseline; the dedicated design governs when details differ.
Replace the current whole-file StringBuilder and direct overwrite with:
- streaming row writer/iterator;
- temporary file in the target directory;
- restrictive creation permissions;
- flush/fsync file, atomic rename when supported, and directory fsync where required;
- explicit fallback when the filesystem cannot guarantee atomic move;
- no-follow-link and real-path containment checks;
- overwrite/precondition policy;
- checksum and manifest;
- size/row/disk-space quota;
- retention/reaper and partial-file cleanup;
- filename/extension/content policy;
- spreadsheet-formula injection defense for CSV/tabular exports, with a tested escaping policy;
- optional encryption and malware scan;
- NFS/SFTP-specific locking, visibility, and rename semantics documented as provider capabilities.
An exported file is identified by an opaque receipt. Absolute server paths are not public API values.
13.5 Identifier and support
adapter:outbound:identifier continues to implement domain/application identifier ports. It may
offer random UUID and time-ordered ID providers, but ordering, clock rollback, collision, encoding,
and database-index tradeoffs are explicit. Pseudonymization keys support secret rotation and never
become reversible identifiers.
adapter:outbound:support remains a small home for provider-neutral outbound decorators and
diagnostic helpers. It does not become a miscellaneous infrastructure module. Fail-open decorators
classify expected dependency failures and do not swallow programming/codec/invariant defects.
14. Inbound transport baselines
14.1 Web
Preserve current validation, error envelope, authz, pagination/cursor, conditional request, OpenAPI, request correlation, and safe cache-control foundations. Add:
- exclusive JWT/session authentication profiles;
- Redis-backed transport-edge rate-limit contract and policy registry;
- actual keyed idempotency executor integration;
- trusted-proxy chain validation;
- request/header/body/multipart limits;
- request deadline and cancellation propagation;
- graceful drain;
- stable API version/deprecation policy;
- OpenAPI compatibility gate;
- CSRF/session cookie tests for stateful mode;
- route-level security/rate/idempotency capability declarations.
14.2 GraphQL
Production baseline:
- shared authentication/tenant context;
- operation and field authorization;
- parser character/token/rule-depth limits;
- query depth and cost/complexity instrumentation;
- persisted-query allowlist profile;
- DataLoader/batch-loader convention and N+1 contract;
- cursor connection and bounded page policy;
- sanitized error extensions;
- introspection/GraphiQL production policy;
- schema snapshot/breaking-change check;
- query duration/complexity/error metrics;
- subscription transport delegated to an explicitly designed WebSocket/messaging path.
14.3 gRPC
Production baseline:
- protobuf generation/versioning convention and compatibility check;
- TLS/mTLS and service/method authorization interceptors;
- required client deadlines and server cancellation propagation;
- request/response and metadata size limits;
- retry policy only for suitable status/method semantics;
- keepalive coordinated with infrastructure;
- unary and streaming backpressure/cancellation;
- standard health status updated during startup/drain/shutdown;
- reflection opt-in outside production;
- graceful shutdown and in-flight drain;
- OTel RPC semantic spans and bounded metrics.
14.4 WebSocket
The simple in-memory STOMP broker remains local/R1 only.
Production baseline:
- authenticated handshake and re-auth/session-expiry behavior;
- destination-level subscribe/send authorization;
- trusted origins and payload/frame limits;
- heartbeat and idle timeout;
- bounded inbound/outbound executors, queues, send time, and an explicit disconnect/drop policy;
- sequence/resume contract where message loss matters;
- per-session ordering only when required and measured;
- broker relay or a durable integration-event bridge for multi-node delivery;
- broker availability/readiness and graceful disconnect;
- no direct serialization of arbitrary domain events to public destinations.
Cross-node durable live updates consume an integration/presentation event. The in-process Spring event bus is not a durable or cluster-wide transport.
15. Observability and operational safety
Signals
- Traces: inbound server, application use case, DB/Redis, messaging producer/consumer, HTTP, object storage, notification, and background-worker spans with standard semantic conventions.
- Metrics: request/dependency latency, errors, saturation, backlog/lag, lease loss, retry, DLT, cache behavior, and provider lifecycle.
- Logs: stable structured schema correlated with trace/span IDs.
- Audit: a separate durable, access-controlled record for security/business actions; not ordinary application logs.
Instrumentation uses one context-propagation owner per transport. Payloads, tokens, Redis keys, session IDs, raw principals, email addresses, and object names are not added to metrics and are allowlisted or pseudonymized in logs/traces.
Production tracing includes a configured OTLP exporter and batch span processor; tests use an
in-memory exporter to prove spans and propagation rather than treating a registry entry as emitted
telemetry. Metrics similarly prove recording, tags, and cardinality. Unmatched or templating-failed
HTTP requests use a fixed route label such as UNKNOWN, never a raw URI.
Health
| Probe | Rule |
|---|---|
| Liveness | JVM/process ability only; never DB, Redis, Kafka, SMTP, object storage, or HTTP dependencies |
| Readiness | enabled providers marked required for this deployment |
| Component health | every enabled provider, including optional cache and notification |
| Startup | configuration, migration, script/schema compatibility, and required topology validation |
Optional cache failure does not restart or necessarily unready the pod. Session, strict idempotency, required lock, or required message publisher failure can make the application unready. The capability descriptor decides; bean name presence does not.
Capacity and runbooks
Each R2/R3 capability includes capacity inputs rather than fabricated numbers:
- key/message/session/object size;
- operation rate and concurrency;
- retention/TTL;
- retry amplification;
- connection/thread/partition counts;
- replica/failover expectations;
- alert thresholds derived from an actual SLO.
Required runbooks cover backlog, lag, DLT, stale lease, Redis memory/noeviction, session outage, connector slot/WAL growth, index drift, multipart orphan, disk capacity, certificate expiry, and provider credential rotation.
16. Gradle and dependency design
Rules:
domain-core,application-core, andshared-contractkeep project-only production dependencies and no Spring starter/SDK.- A real provider dependency lives only in its owning adapter leaf.
implementationis the default.apiis used only when a public contract intentionally exposes a third-party type, which these ports generally forbid.- Spring Boot-managed coordinates use the Boot BOM. Non-Boot SDKs import a provider BOM at module scope, following the existing gRPC/AWS pattern.
- Dependency locks and verification metadata change in the same implementation slice as the dependency.
- Testcontainers, Toxiproxy, embedded brokers, and schema test tools remain test/integration-test dependencies.
- Provider contract kits use Gradle test fixtures or a dedicated test-support source set without becoming production dependencies.
- Integration tests receive a separate
integrationTesttask per provider; architecture checks remain part ofcheck. - Do not introduce a version catalog solely for this work. The existing BOM/module pin model can be retained until dependency ownership itself becomes hard to maintain.
- Tighten registry edges after implementation. Do not add speculative adapter-to-adapter edges.
Expected dependency ownership:
| Dependency family | Owner |
|---|---|
| Spring Data Redis/Lettuce and Spring Session Redis | Redis provider leaf |
| Kafka client/Spring Kafka producer | outbound messaging leaf |
| Kafka listener runtime | future inbound messaging leaf |
| Debezium/Kafka Connect | deployment/integration-test assets, not application-core |
| Mongo driver/Spring Data Mongo | Mongo persistence leaf |
| AWS S3 SDK | object-storage leaf |
| Resilience4j/HTTP engine | HTTP-client leaf |
| gRPC/protobuf runtime/build tooling | gRPC leaf |
| OTel/Micrometer exporter/composition | bootstrap and provider instrumentation adapters |
The new Kafka inbound leaf is the only module addition proposed as structurally necessary in this
design. It requires an explicit modules.json, settings, Gradle dependency-gate, documentation,
and architecture-test migration rather than bypassing the exact-19 assertion.
17. Verification and CI design
Test layers
| Layer | Purpose |
|---|---|
| Pure unit | policy, algorithms, codec/version/key rules, retry/deadline math |
| Port contract | common required-semantics suite plus guarantee/capability-specific provider suites |
| Real-service integration | Redis, PostgreSQL, Mongo replica set, Kafka, MinIO, provider sandbox |
| Concurrency | duplicate claim, token spend, stale release, ordering, session sharing |
| Failure injection | timeout, disconnect, pool exhaustion, restart, failover, network partition |
| Compatibility | serialization, schema, migration, rolling version, Redis/Kafka/Mongo version |
| Architecture | SDK/type/dependency direction and optional-provider gating |
| Operational | health, metrics cardinality, trace propagation, secret/PII absence, graceful shutdown |
Redis tests include standalone and cluster slot behavior, script reload, token mismatch, maxmemory separation, and multiple client connections. CDC tests run PostgreSQL, Kafka, Kafka Connect/Debezium end to end. Session tests use two application contexts against one Redis service.
Providers in different guarantee classes are never certified as semantically identical. For example, local/JDBC/Redis locks and filesystem/S3 storage share only the required contract subset; fencing, conditional writes, multipart, durability, and failover claims require their own capability suite.
CI profiles
- PR gate: unit, architecture, provider contract, and one supported real-service baseline.
- Production-readiness gate: Docker/services are required; absence is a failure, not a silent skip.
- Nightly/weekly matrix: supported datastore/broker versions, cluster/failover, rolling serialization, Toxiproxy, and longer concurrency/soak tests.
- Optional provider sandbox tests use explicit credentials and remain separated from deterministic local protocol tests.
- Performance tests establish product-specific budgets later. This design requires load-test hooks and capacity metrics, not generic benchmark claims.
18. Phased implementation roadmap
Phase 0 — Correctness contracts and truthful capability topology
- replace bean-name multi-instance checks with typed provider descriptors;
- remove or correct registry/settings claims for capabilities that do not exist;
- reconcile notification and JWT keys across the environment registry, YAML, typed settings, and actual conditional beans;
- establish uniform explicit module/provider activation and prohibit missing-property activation of local, plaintext, reflection, or auto-create defaults;
- publish a truthful default bootstrap capability manifest instead of equating source modules with composed runtime features;
- correct and contract-test HTTP retry/circuit-breaker ordering and make the total deadline cancel in-flight work;
- revise idempotency around owner token, lease, and replay TTL;
- distinguish efficiency lock, fenced lock, leadership, semaphore, and work claim;
- define capability cards, readiness levels, settings prefix, failure policy, and common contract test kit;
- fix the local fixed-window key lifecycle or mark it dev-only with bounded storage;
- keep application-core dependency purity and all architecture gates green.
Acceptance: the template cannot start in a configuration that claims an unavailable or weaker provider guarantee.
Phase 1 — Real Redis foundation and cache
- real Spring Data Redis/Lettuce client;
- cache/coordination/session role settings and connections;
- TLS/ACL/timeouts/pool/topology/health;
- versioned codec/key schema;
- cache get/put/evict/bulk/TTL/negative result;
- cache-aside, jitter, single-flight, after-commit invalidation;
- Redis standalone/cluster/failure/observability tests.
Acceptance: cache reaches R2 while correctness Redis roles remain inactive unless selected.
Phase 2 — Distributed rate limit, idempotency, locks, and sessions
- shared transport-edge rate-limit contract plus fixed/sliding-counter/token-bucket Lua providers;
- policy registry and emergency fallback;
- Redis/JPA idempotency contract implementations;
- JDBC/Redis lock provider selection, owner-safe renew/release, fenced lock;
- JWT/Redis-session exclusive profiles and multi-pod session contract.
Acceptance: each selected provider has explicit guarantees and failure behavior; no correctness data uses the evictable cache role.
Phase 3 — Kafka, polling outbox evolution, inbox, and CDC
- acknowledgement-aware real Kafka producer;
- immutable
outbox_eventplus pollingoutbox_delivery; - claim token and aggregate sequence;
- add inbound Kafka leaf and inbox executor/provider;
- retry/DLT/replay/backpressure/graceful lifecycle;
- Debezium connector/deployment assets and end-to-end CDC profile;
- polling/CDC exclusivity and transition runbook.
Acceptance: both dispatch modes independently satisfy at-least-once delivery and idempotent-consumer contracts without an exactly-once claim.
Phase 4 — HTTP, notification, object storage, and file server
- complete HTTP pool/bulkhead/SSRF/TLS/OTel baseline;
- durable notification intent, provider routing, templates, receipts;
- streaming/multipart/presigned/checksum/encryption storage contracts plus staged finalization, compensation, and orphan reconciliation;
- atomic streaming file exports, quotas, retention, CSV formula defense, and filesystem-provider semantics.
Acceptance: each adapter has a real R2 provider, failure injection, health, metrics, and a capability card.
Phase 5 — JPA/Mongo query models and inbound transports
- read consistency and replica routing;
- same-store and separate read-model profiles with checkpoint/lag;
- Mongo concerns/indexes/migrations/transactions/change streams;
- GraphQL complexity/DataLoader/schema gates;
- gRPC TLS/auth/deadline/streaming/proto gates;
- WebSocket broker relay/backpressure/auth/cluster behavior;
- route/operation capability declarations across transports.
Acceptance: query and transport choices are explicit and operationally observable without leaking transport or persistence types into core.
Phase 6 — R3 scale and extraction review
- failover, rolling upgrade, version matrix, recovery drills, and capacity runbooks;
- evaluate splitting Redis capability-provider leaves;
- evaluate extracting a platform BOM/starter only after multiple real consumers validate the APIs.
19. Completion criteria for the future implementation
The implementation is complete only when:
- every enabled capability has a real provider rather than a project-supplied seam;
- provider selection is exact, typed, and fail-fast;
- the provider guarantee and non-guarantees are visible;
- unused capabilities have no runtime side effects;
- core modules remain framework/SDK-free;
- every provider passes reusable contract plus real-service/failure tests;
- correctness and optimization data stores are separated where eviction/failure semantics differ;
- health, metrics, traces, logs, graceful lifecycle, security, and runbook are present;
- CI has a non-skipping production-readiness path;
- no cross-database exactly-once or strong Redis-lock claim appears in code or documentation.
20. Primary references
- Redis scripting and atomic blocking semantics
- Redis rate-limiter use case and algorithm options
- Redis key eviction
- Redis distributed locks and fencing guidance
- Spring Data Redis scripting
- Spring Session Redis APIs
- Debezium Outbox Event Router
- Apache Kafka delivery semantics and transactions
- Apache Kafka producer configuration
- Resilience4j fault-tolerance primitives
- Amazon S3 object-integrity checks
- MongoDB read concern
- MongoDB write concern
- MongoDB change streams
- GraphQL Java query limits
- gRPC deadlines
- gRPC retry
- Spring WebSocket external broker relay
- OpenTelemetry signals and semantic conventions