Add AICL example: 19_observer_system.aicl
Browse files
data/aicl/examples/19_observer_system.aicl
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AICL Example: Observer Pattern Event Bus
|
| 2 |
+
# Implements a robust publish/subscribe event bus with topic routing, guaranteed message ordering,
|
| 3 |
+
# backpressure management, dead letter queue handling, and replay capabilities.
|
| 4 |
+
|
| 5 |
+
# Level 1: Architecture
|
| 6 |
+
Goal: Build a high-throughput event bus implementing the Observer pattern with topic-based routing,
|
| 7 |
+
strict per-topic message ordering, backpressure to protect slow subscribers,
|
| 8 |
+
dead letter queues for poison messages, and replay support for missed events.
|
| 9 |
+
|
| 10 |
+
Constraint: Publishers must be decoupled from subscribers; no direct references allowed.
|
| 11 |
+
Constraint: Each topic maintains FIFO ordering within a partition key.
|
| 12 |
+
Constraint: Subscribers must acknowledge messages within a configurable timeout.
|
| 13 |
+
Constraint: Backpressure must be applied before memory pressure reaches critical threshold.
|
| 14 |
+
Constraint: Dead letter messages must never be silently dropped; always persisted for inspection.
|
| 15 |
+
|
| 16 |
+
Risk: Slow subscriber causes unbounded buffer growth
|
| 17 |
+
Recovery: Apply per-subscriber backpressure with configurable max buffer; overflow goes to DLQ
|
| 18 |
+
|
| 19 |
+
Risk: Message ordering violation under concurrent publishing
|
| 20 |
+
Recovery: Partition by routing key; single-writer per partition with sequencer
|
| 21 |
+
|
| 22 |
+
Risk: Dead letter queue grows unbounded without reprocessing
|
| 23 |
+
Recovery: Scheduled DLQ reprocessor with exponential backoff; alert at queue depth threshold
|
| 24 |
+
|
| 25 |
+
Risk: Subscriber crash loses in-flight messages
|
| 26 |
+
Recovery: Redeliver unacknowledged messages after visibility timeout with at-least-once guarantee
|
| 27 |
+
|
| 28 |
+
Risk: Topic routing misconfiguration routes messages to wrong subscribers
|
| 29 |
+
Recovery: Validate topic subscription patterns at registration; audit log all routing decisions
|
| 30 |
+
|
| 31 |
+
Risk: Replay floods subscribers with historical volume
|
| 32 |
+
Recovery: Throttle replay delivery rate; subscribers opt-in to replay with max throughput setting
|
| 33 |
+
|
| 34 |
+
Layer: Core
|
| 35 |
+
SubLayer: Publisher
|
| 36 |
+
SubLayer: TopicRouter
|
| 37 |
+
SubLayer: PartitionManager
|
| 38 |
+
Layer: Subscription
|
| 39 |
+
SubLayer: SubscriberRegistry
|
| 40 |
+
SubLayer: SubscriptionManager
|
| 41 |
+
SubLayer: AcknowledgmentTracker
|
| 42 |
+
Layer: Reliability
|
| 43 |
+
SubLayer: BackpressureController
|
| 44 |
+
SubLayer: DeadLetterQueue
|
| 45 |
+
SubLayer: ReplayEngine
|
| 46 |
+
|
| 47 |
+
Validation: Topic name must match [a-zA-Z][a-zA-Z0-9_.-] pattern
|
| 48 |
+
Validation: Subscriber callback must complete within acknowledgment timeout
|
| 49 |
+
Validation: Partition key must be present for ordered topics
|
| 50 |
+
Validation: Replay request must specify valid time range and subscriber ID
|
| 51 |
+
Validation: Backpressure threshold must be positive and less than system memory limit
|
| 52 |
+
Validation: Dead letter message must preserve original headers and failure reason
|
| 53 |
+
|
| 54 |
+
# Level 2: Entities
|
| 55 |
+
Entity Topic
|
| 56 |
+
topicId: string
|
| 57 |
+
topicName: string
|
| 58 |
+
partitionCount: integer
|
| 59 |
+
retentionHours: integer
|
| 60 |
+
isOrdered: boolean
|
| 61 |
+
createdAt: datetime
|
| 62 |
+
messageCount: integer
|
| 63 |
+
byteRate: float
|
| 64 |
+
schema: dict
|
| 65 |
+
|
| 66 |
+
Entity Subscription
|
| 67 |
+
subscriptionId: string
|
| 68 |
+
subscriberName: string
|
| 69 |
+
topicPattern: string
|
| 70 |
+
callback: string
|
| 71 |
+
filterExpression: string
|
| 72 |
+
maxInFlight: integer
|
| 73 |
+
ackTimeout: integer
|
| 74 |
+
retryPolicy: dict
|
| 75 |
+
createdAt: datetime
|
| 76 |
+
|
| 77 |
+
Entity Message
|
| 78 |
+
messageId: string
|
| 79 |
+
topic: string
|
| 80 |
+
partitionKey: string
|
| 81 |
+
sequenceNumber: integer
|
| 82 |
+
payload: dict
|
| 83 |
+
headers: dict
|
| 84 |
+
publishedAt: datetime
|
| 85 |
+
expiresAt: datetime
|
| 86 |
+
traceId: string
|
| 87 |
+
|
| 88 |
+
Entity DeadLetterEntry
|
| 89 |
+
entryId: string
|
| 90 |
+
originalMessage: Message
|
| 91 |
+
subscriberName: string
|
| 92 |
+
failureReason: string
|
| 93 |
+
failureCount: integer
|
| 94 |
+
firstFailedAt: datetime
|
| 95 |
+
lastFailedAt: datetime
|
| 96 |
+
nextRetryAt: datetime
|
| 97 |
+
deadLetteredAt: datetime
|
| 98 |
+
|
| 99 |
+
Entity BackpressureState
|
| 100 |
+
subscriberId: string
|
| 101 |
+
currentBufferSize: integer
|
| 102 |
+
maxBufferSize: integer
|
| 103 |
+
pressureLevel: string
|
| 104 |
+
throttleRate: float
|
| 105 |
+
droppedCount: integer
|
| 106 |
+
lastAdjustedAt: datetime
|
| 107 |
+
|
| 108 |
+
Entity SubscriberMetrics
|
| 109 |
+
subscriberId: string
|
| 110 |
+
messagesProcessed: integer
|
| 111 |
+
messagesFailed: integer
|
| 112 |
+
averageLatency: float
|
| 113 |
+
p99Latency: float
|
| 114 |
+
lastMessageAt: datetime
|
| 115 |
+
activeSubscriptions: integer
|
| 116 |
+
dlqDepth: integer
|
| 117 |
+
|
| 118 |
+
# Level 3: Behaviors
|
| 119 |
+
Behavior PublishMessage
|
| 120 |
+
Input:
|
| 121 |
+
topic: string
|
| 122 |
+
partitionKey: string
|
| 123 |
+
payload: dict
|
| 124 |
+
headers: dict
|
| 125 |
+
Output:
|
| 126 |
+
messageId: string
|
| 127 |
+
sequenceNumber: integer
|
| 128 |
+
Action:
|
| 129 |
+
Validate topic exists and schema matches payload
|
| 130 |
+
Determine partition from partition key hash
|
| 131 |
+
Assign monotonic sequence number within partition
|
| 132 |
+
Persist message to topic log
|
| 133 |
+
Route message to all matching subscriptions
|
| 134 |
+
Apply backpressure check per subscriber before delivery
|
| 135 |
+
Return message ID and sequence number
|
| 136 |
+
|
| 137 |
+
Behavior Subscribe
|
| 138 |
+
Input:
|
| 139 |
+
topicPattern: string
|
| 140 |
+
callback: string
|
| 141 |
+
config: dict
|
| 142 |
+
Output:
|
| 143 |
+
subscriptionId: string
|
| 144 |
+
Action:
|
| 145 |
+
Validate topic pattern syntax and matching topics
|
| 146 |
+
Register subscription in subscriber registry
|
| 147 |
+
Initialize delivery buffer and backpressure controller
|
| 148 |
+
Begin message delivery from current head
|
| 149 |
+
Return subscription identifier
|
| 150 |
+
|
| 151 |
+
Behavior AcknowledgeMessage
|
| 152 |
+
Input:
|
| 153 |
+
subscriptionId: string
|
| 154 |
+
messageId: string
|
| 155 |
+
status: string
|
| 156 |
+
Output:
|
| 157 |
+
acknowledged: boolean
|
| 158 |
+
Action:
|
| 159 |
+
Verify message belongs to subscription's in-flight set
|
| 160 |
+
If status is success, remove from in-flight and advance checkpoint
|
| 161 |
+
If status is failure, apply retry policy or move to DLQ
|
| 162 |
+
Update subscriber metrics
|
| 163 |
+
Release backpressure slot
|
| 164 |
+
Return acknowledgment confirmation
|
| 165 |
+
|
| 166 |
+
Behavior RouteToSubscriber
|
| 167 |
+
Input:
|
| 168 |
+
message: Message
|
| 169 |
+
subscription: Subscription
|
| 170 |
+
Output:
|
| 171 |
+
delivered: boolean
|
| 172 |
+
Action:
|
| 173 |
+
Evaluate filter expression against message headers
|
| 174 |
+
Check subscriber backpressure state
|
| 175 |
+
If pressure is critical, route to DLQ with backpressure reason
|
| 176 |
+
If pressure is moderate, apply throttle delay
|
| 177 |
+
If acceptable, deliver to subscriber callback
|
| 178 |
+
Track delivery in acknowledgment tracker
|
| 179 |
+
Return delivery result
|
| 180 |
+
|
| 181 |
+
Behavior ProcessDeadLetter
|
| 182 |
+
Input:
|
| 183 |
+
entryId: string
|
| 184 |
+
action: string
|
| 185 |
+
Output:
|
| 186 |
+
result: string
|
| 187 |
+
Action:
|
| 188 |
+
Load dead letter entry from DLQ store
|
| 189 |
+
If action is retry, re-inject message to original subscription
|
| 190 |
+
If action is discard, log and permanently remove
|
| 191 |
+
If action is redirect, publish to alternate topic
|
| 192 |
+
Update DLQ metrics and entry status
|
| 193 |
+
Return action result
|
| 194 |
+
|
| 195 |
+
Behavior ReplayMessages
|
| 196 |
+
Input:
|
| 197 |
+
subscriptionId: string
|
| 198 |
+
fromTimestamp: datetime
|
| 199 |
+
toTimestamp: datetime
|
| 200 |
+
maxThroughput: integer
|
| 201 |
+
Output:
|
| 202 |
+
replayId: string
|
| 203 |
+
messageCount: integer
|
| 204 |
+
Action:
|
| 205 |
+
Validate subscription and time range
|
| 206 |
+
Load messages from topic log for time range
|
| 207 |
+
Apply max throughput throttling
|
| 208 |
+
Deliver messages to subscriber callback
|
| 209 |
+
Skip already-acknowledged messages
|
| 210 |
+
Track replay progress and completion
|
| 211 |
+
Return replay statistics
|
| 212 |
+
|
| 213 |
+
# Level 4: Conditions
|
| 214 |
+
Condition: BackpressureCritical
|
| 215 |
+
When subscriber buffer reaches 90% of max capacity
|
| 216 |
+
Then redirect new messages to DLQ with backpressure flag and alert subscriber owner
|
| 217 |
+
|
| 218 |
+
Condition: AcknowledgmentTimeout
|
| 219 |
+
When message remains unacknowledged beyond ackTimeout
|
| 220 |
+
Then redeliver message with incremented delivery attempt counter; after max attempts move to DLQ
|
| 221 |
+
|
| 222 |
+
Condition: InvalidMessageSchema
|
| 223 |
+
When message payload does not conform to topic schema
|
| 224 |
+
Then reject publication with validation error; if already published, route to DLQ
|
| 225 |
+
|
| 226 |
+
# Level 5: Events
|
| 227 |
+
Event: OnSubscriberRegistered
|
| 228 |
+
On new subscription created for a topic pattern
|
| 229 |
+
Action: Warm subscriber connection pool and validate callback endpoint reachability
|
| 230 |
+
|
| 231 |
+
Event: OnBackpressureThreshold
|
| 232 |
+
On subscriber buffer crosses 75% capacity
|
| 233 |
+
Action: Apply throttle, notify subscriber via health endpoint, and begin buffering upstream
|
| 234 |
+
|
| 235 |
+
Event: OnDeadLetterCreated
|
| 236 |
+
On message moved to dead letter queue
|
| 237 |
+
Action: Emit metric, send notification to subscriber owner, and log for audit trail
|
| 238 |
+
|
| 239 |
+
Event: OnReplayCompleted
|
| 240 |
+
On message replay batch fully delivered
|
| 241 |
+
Action: Log replay summary, update subscriber checkpoint, and emit completion metric
|
| 242 |
+
|
| 243 |
+
# Level 6: Concurrency
|
| 244 |
+
Parallel:
|
| 245 |
+
Message publishing to multiple partitions concurrently
|
| 246 |
+
Message delivery to independent subscribers in parallel
|
| 247 |
+
Dead letter queue reprocessing on separate worker pool
|
| 248 |
+
Backpressure monitoring and adjustment across all subscribers
|
| 249 |
+
Metrics aggregation from all subscriber delivery threads
|
| 250 |
+
|
| 251 |
+
# Level 7: Optimization
|
| 252 |
+
Optimize: Message delivery throughput
|
| 253 |
+
Priority: Batch deliveries per subscriber; minimize lock contention on partition sequencer
|
| 254 |
+
|
| 255 |
+
Optimize: Topic routing latency
|
| 256 |
+
Priority: Cache subscription pattern matching results; pre-compile filter expressions
|
| 257 |
+
|
| 258 |
+
# Level 8: Learning
|
| 259 |
+
Learn: Subscriber processing capacity
|
| 260 |
+
Goal: Predict optimal max-in-flight setting per subscriber to maximize throughput without overflow
|
| 261 |
+
Adapt: Per-subscriber maxInFlight and buffer size
|
| 262 |
+
Based: Subscriber acknowledgment rate and latency distribution over sliding window
|
| 263 |
+
|
| 264 |
+
Learn: DLQ root cause patterns
|
| 265 |
+
Goal: Categorize dead-lettered messages by failure type to identify systemic issues
|
| 266 |
+
Adapt: Alerting rules and auto-retry thresholds per failure category
|
| 267 |
+
Based: Historical DLQ entry failure reason distribution and retry success rates
|
| 268 |
+
|
| 269 |
+
# Level 9: Security
|
| 270 |
+
Security:
|
| 271 |
+
Encrypt: Message payload in transit via TLS 1.3 with pinned certificates between bus and subscribers
|
| 272 |
+
Encrypt: DLQ entries at rest using AES-256 with per-topic encryption keys
|
| 273 |
+
Protect: Topic publication with IAM-style authorization and topic-level ACL
|
| 274 |
+
Protect: Subscription callbacks from message injection via HMAC message signing
|
| 275 |
+
Protect: Replay endpoints from unauthorized access with admin role requirement and audit logging
|
| 276 |
+
|
| 277 |
+
# Level 10: Native
|
| 278 |
+
Rust
|
| 279 |
+
{
|
| 280 |
+
use std::collections::HashMap;
|
| 281 |
+
use std::sync::{Arc, Mutex};
|
| 282 |
+
use std::time::{Duration, Instant};
|
| 283 |
+
|
| 284 |
+
#[derive(Clone, Debug)]
|
| 285 |
+
pub struct Message {
|
| 286 |
+
pub message_id: String,
|
| 287 |
+
pub topic: String,
|
| 288 |
+
pub partition_key: String,
|
| 289 |
+
pub sequence_number: u64,
|
| 290 |
+
pub payload: Vec<u8>,
|
| 291 |
+
pub headers: HashMap<String, String>,
|
| 292 |
+
pub published_at: Instant,
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
#[derive(Clone, Debug)]
|
| 296 |
+
pub struct Subscription {
|
| 297 |
+
pub subscription_id: String,
|
| 298 |
+
pub subscriber_name: String,
|
| 299 |
+
pub topic_pattern: String,
|
| 300 |
+
pub max_in_flight: usize,
|
| 301 |
+
pub ack_timeout: Duration,
|
| 302 |
+
pub retry_max: u32,
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
#[derive(Debug)]
|
| 306 |
+
pub enum DeliverStatus {
|
| 307 |
+
Acknowledged,
|
| 308 |
+
Failed(String),
|
| 309 |
+
Timeout,
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
pub struct DeadLetterEntry {
|
| 313 |
+
pub entry_id: String,
|
| 314 |
+
pub original_message: Message,
|
| 315 |
+
pub subscriber_name: String,
|
| 316 |
+
pub failure_reason: String,
|
| 317 |
+
pub failure_count: u32,
|
| 318 |
+
pub first_failed_at: Instant,
|
| 319 |
+
pub dead_lettered_at: Instant,
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
pub struct EventBus {
|
| 323 |
+
topics: Arc<Mutex<HashMap<String, Vec<Message>>>>,
|
| 324 |
+
subscriptions: Arc<Mutex<Vec<Subscription>>>,
|
| 325 |
+
in_flight: Arc<Mutex<HashMap<String, (Message, Instant, u32)>>>,
|
| 326 |
+
dead_letter_queue: Arc<Mutex<Vec<DeadLetterEntry>>>,
|
| 327 |
+
max_buffer: usize,
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
impl EventBus {
|
| 331 |
+
pub fn new(max_buffer: usize) -> Self {
|
| 332 |
+
EventBus {
|
| 333 |
+
topics: Arc::new(Mutex::new(HashMap::new())),
|
| 334 |
+
subscriptions: Arc::new(Mutex::new(Vec::new())),
|
| 335 |
+
in_flight: Arc::new(Mutex::new(HashMap::new())),
|
| 336 |
+
dead_letter_queue: Arc::new(Mutex::new(Vec::new())),
|
| 337 |
+
max_buffer,
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
pub fn publish(&self, topic: &str, msg: Message) -> u64 {
|
| 342 |
+
let mut topics = self.topics.lock().unwrap();
|
| 343 |
+
let partition = topics.entry(topic.to_string()).or_insert_with(Vec::new);
|
| 344 |
+
let seq = partition.len() as u64;
|
| 345 |
+
let mut msg = msg;
|
| 346 |
+
msg.sequence_number = seq;
|
| 347 |
+
partition.push(msg.clone());
|
| 348 |
+
seq
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
pub fn subscribe(&self, sub: Subscription) {
|
| 352 |
+
let mut subs = self.subscriptions.lock().unwrap();
|
| 353 |
+
subs.push(sub);
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
pub fn acknowledge(&self, subscription_id: &str, message_id: &str) -> bool {
|
| 357 |
+
let mut in_flight = self.in_flight.lock().unwrap();
|
| 358 |
+
in_flight.remove(&format!("{}:{}", subscription_id, message_id)).is_some()
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
pub fn check_timeouts(&self) {
|
| 362 |
+
let mut in_flight = self.in_flight.lock().unwrap();
|
| 363 |
+
let mut dlq = self.dead_letter_queue.lock().unwrap();
|
| 364 |
+
let now = Instant::now();
|
| 365 |
+
let expired: Vec<String> = in_flight.iter()
|
| 366 |
+
.filter(|(_, (msg, sent_at, retries))| {
|
| 367 |
+
now.duration_since(*sent_at) > Duration::from_secs(30) && *retries >= 3
|
| 368 |
+
})
|
| 369 |
+
.map(|(k, _)| k.clone())
|
| 370 |
+
.collect();
|
| 371 |
+
for key in expired {
|
| 372 |
+
if let Some((msg, _, retries)) = in_flight.remove(&key) {
|
| 373 |
+
dlq.push(DeadLetterEntry {
|
| 374 |
+
entry_id: format!("dlq-{}", msg.message_id),
|
| 375 |
+
original_message: msg,
|
| 376 |
+
subscriber_name: String::new(),
|
| 377 |
+
failure_reason: "ack_timeout_exceeded".to_string(),
|
| 378 |
+
failure_count: retries,
|
| 379 |
+
first_failed_at: now,
|
| 380 |
+
dead_lettered_at: now,
|
| 381 |
+
});
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
pub fn dead_letter_count(&self) -> usize {
|
| 387 |
+
self.dead_letter_queue.lock().unwrap().len()
|
| 388 |
+
}
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
fn main() {
|
| 392 |
+
let bus = EventBus::new(10_000);
|
| 393 |
+
let sub = Subscription {
|
| 394 |
+
subscription_id: "sub-1".to_string(),
|
| 395 |
+
subscriber_name: "order-service".to_string(),
|
| 396 |
+
topic_pattern: "orders.*".to_string(),
|
| 397 |
+
max_in_flight: 50,
|
| 398 |
+
ack_timeout: Duration::from_secs(30),
|
| 399 |
+
retry_max: 3,
|
| 400 |
+
};
|
| 401 |
+
bus.subscribe(sub);
|
| 402 |
+
let msg = Message {
|
| 403 |
+
message_id: "msg-1".to_string(),
|
| 404 |
+
topic: "orders.created".to_string(),
|
| 405 |
+
partition_key: "order-123".to_string(),
|
| 406 |
+
sequence_number: 0,
|
| 407 |
+
payload: vec![],
|
| 408 |
+
headers: HashMap::new(),
|
| 409 |
+
published_at: Instant::now(),
|
| 410 |
+
};
|
| 411 |
+
let seq = bus.publish("orders.created", msg);
|
| 412 |
+
println!("Published message with sequence: {}", seq);
|
| 413 |
+
}
|