Target Architecture Specification (v1)¶
This specification defines the target runtime, concurrency model, and messaging architecture for DKV Pet Flows under the Spring Boot 4 + Apache Pekko stack.
1. System Topology¶
The system is designed to be decoupled from core servers. Event ingestion is managed asynchronously via HTTP Webhooks and RabbitMQ queues. A local Apache Pekko Actor System orchestrates the flow state machine internally.
Direct synchronous HTTP REST connections are established to dkv-pet-cloud for:
- Authorization: Validating client tokens and API keys when webhooks are called.
- Queue Recovery: Requesting event playbacks from a specific offset or timestamp during system initialization or recovery phases.
graph TD
subgraph DKV Core
Cloud["dkv-pet-cloud"]
Netcomp["netcomp"]
end
subgraph DKV Pet Flows API (Spring Boot 4)
Webhooks["Webhook Ingestion API (/api/v1/events)"]
QueueListener["RabbitMQ Event Listener"]
PekkoBridge["Spring-Pekko Lifecycle Bridge"]
AuthClient["REST Auth Client"]
RecoveryClient["REST Queue Recovery Client"]
subgraph Local Pekko Actor System
Supervisor["Flow Supervisor Actor"]
CampaignActor["Campaign Actor (One per Campaign)"]
FlowActor["User Flow Actor (Stateful)"]
Dispatcher["Channel Dispatcher Actor"]
end
DB[("PostgreSQL (State Store)")]
end
subgraph Channels
APNs["Gorush (Push APNs/FCM)"]
SMS["SMS Gateway"]
Email["Email Service (SMTP)"]
end
%% Decoupled Ingestion
Cloud -->|HTTP POST Webhook| Webhooks
Netcomp -->|AMQP Pub| QueueListener
%% Synchronous Auth & Recovery
Webhooks -->|Validate Token| AuthClient
AuthClient -->|REST GET /auth| Cloud
PekkoBridge -->|Trigger Event Playback| RecoveryClient
RecoveryClient -->|REST POST /events/replay| Cloud
%% In-App Dispatching
Webhooks -->|Dispatch Command| PekkoBridge
QueueListener -->|Dispatch Command| PekkoBridge
PekkoBridge -->|Command| Supervisor
%% Actor Hierarchy
Supervisor -->|Creates / Manages| CampaignActor
CampaignActor -->|Spawns| FlowActor
FlowActor -->|Send Alert| Dispatcher
%% Persistence
FlowActor <-->|JDBC/R2DBC| DB
%% Outbound
Dispatcher -->|Push API| APNs
Dispatcher -->|SMS API| SMS
Dispatcher -->|SMTP| Email
2. Concurrency Model (Project Loom)¶
To avoid complex reactive streams (Mutiny or Reactor) while maintaining high throughput, the system combines Project Loom (Virtual Threads) and the Actor Model (Pekko).
- Edge Layer (Spring MVC + Virtual Threads): Standard thread-per-request model, but execution is delegated to lightweight Virtual Threads. Heavy blocking I/O (handling incoming HTTP webhook requests, database persistence checks, parsing large JSON payloads) is processed without pinning OS platform threads.
- Orchestration Layer (Pekko Actors): Highly specialized stateful computations, timers, rate limiting, and workflow branching. Actors process messages sequentially inside a dedicated Dispatcher, ensuring lock-free concurrency.
Incoming Request ──> [Spring Web Controller] (Virtual Thread - Blocking I/O OK)
│
▼ (Non-blocking Dispatch)
[Pekko Actor System] (Sequential processing, Lock-free state)
3. Actor Hierarchy & Protocol¶
A hierarchical supervisor strategy guarantees high resilience. If a single User Flow Actor fails during execution, the failure is isolated and does not disrupt the entire system.
graph TD
System["Pekko ActorSystem"]
Supervisor["/user/flow-supervisor"]
CampaignA["/user/flow-supervisor/campaign-policy-renewals"]
CampaignB["/user/flow-supervisor/campaign-welcome-onboarding"]
UserFlow1["UserFlowActor (user-101)"]
UserFlow2["UserFlowActor (user-102)"]
Dispatcher["/user/channel-dispatcher"]
System --> Supervisor
Supervisor --> CampaignA
Supervisor --> CampaignB
CampaignA --> UserFlow1
CampaignA --> UserFlow2
UserFlow1 --> Dispatcher
UserFlow2 --> Dispatcher
Actor Roles¶
- Flow Supervisor: Lifecycle coordinator. Listens to system startup/shutdown events, parses configuration, and instantiates Campaign Actors.
- Campaign Actor: Orchestrator of a specific campaign. Handles batch starts, rate-limiting constraints, and acts as a router/parent to individual User Flow Actors.
- User Flow Actor: Stateful executor for a specific user within a campaign. Maintains progress (e.g., "Step 1: Wait 24h", "Step 2: Send Push"). Persists state dynamically.
- Channel Dispatcher: Handles integration with external communication systems (Gorush APNs/FCM, SMS gateway, Email). Isolates network I/O timeouts.
4. Messaging Protocol (CBOR Serialization)¶
To protect the Actor System and allow future cluster extensions safely, all messages (Commands, Events, States) must utilize binary CBOR (Concise Binary Object Representation) serialization instead of standard Java serialization.
Strict CBOR Mapping Rules¶
- All message classes must implement a marker interface
com.dkv.pet.flows.serialization.CborSerializable. - Avoid direct Scala types or Jackson native
JsonNodefields inside messages. Use standard immutable Java types (record,List,Map). - Serialization config (
application.conf):
pekko {
actor {
serializers {
jackson-cbor = "org.apache.pekko.serialization.jackson.JacksonCborSerializer"
}
serialization-bindings {
"com.dkv.pet.flows.serialization.CborSerializable" = jackson-cbor
}
}
}
5. Scale & Performance Targets¶
Handling campaigns with 100,000+ simultaneous actors:
- Memory Footprint: Active Pekko actors consume ~300 bytes of overhead each. 100k active actors in memory consume less than 40MB RAM.
- Passivation: Actors that are waiting (e.g., "Wait 3 days before next follow-up") are passivated (state persisted to PostgreSQL, actor stopped) to preserve heap memory. They are re-hydrated dynamically upon receiving a new event or when their timer expires.
- Database Access: Leverages Spring Data JPA with batch updates and optimized connection pooling (HikariCP).
6. Concurrency Integration & Non-Blocking Persistence¶
To prevent carrier thread pinning under Project Loom when calling blocking JDBC/JPA libraries, Pekko actors must never execute database I/O directly within their message-handling loop. Instead, database interactions are strictly decoupled.
Asynchronous Write-Behind & Blocking Dispatcher¶
- Dedicated Dispatcher: A custom Pekko dispatcher (
db-dispatcher) is configured with a dedicated thread pool for blocking operations. - Persistence Message Protocol: The
UserFlowActordoes not block. It forwards state snapshots to a thread-safeDatabasePersisteractor, which runs exclusively on thedb-dispatcher. - Execution Path:
[UserFlowActor] ──(Forward State Snapshot)──> [DatabasePersister] │ ▼ (Runs on db-dispatcher thread) [Spring Data JPA Write]
7. State Migration (Read-Through Fallback)¶
To migrate active user flows from the experimental Dapr state store (Redis/PostgreSQL) without breaking active user workflows:
sequenceDiagram
autonumber
actor Cloud as dkv-pet-cloud
participant Controller as Ingestion Controller
participant Supervisor as Flow Supervisor
participant FlowActor as User Flow Actor
database NewDB as New PostgreSQL
database LegacyDB as Legacy Dapr Store
Cloud->>Controller: HTTP Webhook Event (user-101)
Controller->>Supervisor: IngestEventCmd
Supervisor->>FlowActor: Rehydrate / Start
FlowActor->>NewDB: Query current state
NewDB-->>FlowActor: State NOT FOUND (New User/In-Flight Pivot)
rect rgb(230, 240, 255)
note right of FlowActor: Read-Through Fallback Triggered
FlowActor->>LegacyDB: Query legacy Dapr state
LegacyDB-->>FlowActor: Hydrated State (Step 2: Wait 24h)
FlowActor->>NewDB: Save hydrated state to new schema
end
FlowActor->>FlowActor: Process incoming event with restored state
8. Webhook Ingestion Security (HMAC Validation)¶
Incoming HTTP Webhooks at /api/v1/events from dkv-pet-cloud must be authenticated using high-speed HMAC-SHA256 signature verification at the edge layer.
- Header:
X-DKV-Signature - Mechanism: The Edge Webhook Controller calculates the HMAC of the raw request payload using a shared webhook secret and compares it in constant-time with the header value.
- Loom Benefit: Payload signature computing and verification are offloaded to Java Virtual Threads, keeping execution non-blocking and highly parallel.
9. Ingestion Backpressure & Fault Tolerance¶
To handle high traffic surges and downstream system failures, the architecture implements native rate-limiting and isolation boundaries.
A. RabbitMQ Ingestion Backpressure¶
To prevent memory exhaustion during event storms:
- The RabbitMQ consumer is configured with a strict prefetchCount = 200.
- Consumers pull events from queues only when local actors have finished processing pending command queues, creating a natural backpressure boundary.
B. Outbound Channel Fault Tolerance (Gorush / SMS Gateway)¶
The ChannelDispatcher actor utilizes a BackoffSupervisor pattern:
- Exponential Backoff: If an outbound dispatch fails due to external API timeouts or 5xx errors, the actor restarts with exponential backoff (minBackoff = 1s, maxBackoff = 30s, randomFactor = 0.2).
- Dead Letter Queue (DLQ): After 5 unsuccessful delivery attempts, messages are directed to a Dead Letter Queue for auditing and manual playback.
10. Deployment Canary & Graceful Shutdown¶
To release new versions of the stateful API without losing active campaigns or causing duplicate actions:
- Graceful Passivation: Upon receiving
SIGTERM, Spring's lifecycle hooks trigger a coordinated shutdown sequence. - Coordinated Shutdown Phase:
- Stop RabbitMQ consumers immediately to halt new ingestion.
- Deny new incoming webhook requests (return
503 Service Unavailablewith active retry hints). - Allow active actor messages in-flight to finish.
- Force passivation of all memory-resident
UserFlowActorinstances, flushing their state snapshots to PostgreSQL. - Wait for outbound
ChannelDispatcherbuffers to empty. - Talos Kubernetes Integration: The pod definition configures
terminationGracePeriodSeconds = 60to ensure the coordinated shutdown completes fully.