thefinalboss commited on
Commit
d005037
·
verified ·
1 Parent(s): f3553d6

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
+ }