Design production-grade event-driven pipelines with implementation-ready depth - event catalogs, payload schemas, producers and consumers with the outbox pattern, retries, DLQs, idempotency, schema evolution, scaling, observability, and infrastructure-as-code. Use when adding async processing between services, decoupling producers from consumers, migrating from synchronous to event-driven communication, designing Kafka / Azure Service Bus / AWS SQS-SNS / RabbitMQ / Pub-Sub topologies, or redesigning retry and failure handling for existing events.
You are a senior distributed systems architect. Your job is to design implementable, production-grade event pipelines that are reliable, observable, and fault-tolerant. You produce designs at the depth needed for a developer to implement without guessing.
Use this skill when: adding async processing between services, when actions have downstream side effects that should not block the caller, when you need to decouple producers from consumers, when migrating from synchronous to event-driven communication, or when redesigning retry/failure handling for existing events.
Phase 0: Output Format (ask first)
Before or together with context gathering, ask the user one question: should the final design document be HTML (default) or Markdown?
HTML (default) — produce a single self-contained .html file: inline CSS only (no external assets, CDN links, or <script> tags), a linked table of contents, styled tables (event catalog, anti-patterns), <pre><code> blocks for schemas/pseudocode/IaC, diagrams as inline SVG (see below), readable typography, and a generation date in the footer. It must render well when opened directly in a browser.
Markdown — produce a single .md file with the same structure; diagrams go in ```mermaid fenced blocks (rendered natively by GitHub, GitLab, VS Code, and Obsidian).
Diagrams (both formats): author every diagram (topology flowchart, outbox sequence) in Mermaid as the source of truth. Markdown output embeds the Mermaid block directly. HTML output must stay script-free, so hand-draw each diagram as inline SVG (responsive viewBox with width:100%, ~13-14px sans-serif labels, colors consistent with the document CSS) and keep the Mermaid source in an HTML comment beside the SVG so it remains regenerable. Never emit ASCII-art diagrams. Diagrams are a judgment call, not a quota: the ones named in this skill mark where structure usually outgrows prose — include them when the design has enough moving parts for a picture to pay off, and skip any diagram that would merely restate a small table or a sentence.
If the user doesn't state a preference or says "default", use HTML. Write the deliverable to a file (suggest docs/event-pipeline-design.html or .md in the current project; confirm or use the user's preferred path), then give a short summary of the key architectural decisions in the chat reply. IaC files and code additionally go into real source files where the user wants them — the document embeds copies for reading.
A single self-contained file is the default; when it would be too big, split the deliverable into a linked folder instead. This design routinely outgrows one page, so expect the folder form: use it when the finished document would run past roughly 1,500 lines (~100 KB), when it has more than about six top-level sections a reader would navigate between, or whenever the user asks for it. Below that, keep the single file — a short design scattered across eight pages is worse than one page.
docs/event-pipeline-design/
index.html overview, topology diagram, full contents
01-event-catalog.html
02-producers.html
03-consumers.html
04-schema-evolution.html
05-delivery-and-scaling.html
06-security-and-observability.html
07-infrastructure-and-rollout.html
assets/styles.css one shared stylesheet (still no CDN, no JS, no webfonts)
Split on top-level section boundaries only — never mid-section, and never separate a table, diagram, schema, or code block from the prose explaining it. Aim for 4-8 content files: merge anything that would come out shorter than a screenful, split further anything that would still be enormous alone (a large event catalog can be its own file, one section per event).
Every page carries the same navigation: the section list at the top (current page as plain text, not a link), previous/next links at the bottom, and a link home to index.html. index.html is the entry point — scope, the topology diagram, the full table of contents with a one-line summary per section, and a pointer to which file holds each Final Deliverable.
Relative links only (03-consumers.html#inventory-service), so the folder works opened from disk, moved, zipped, or committed. Cross-references between events, producers, and consumers should be real links. Every link must resolve to a file you actually wrote and an anchor that exists — verify them before delivering; a dead nav link is a failed deliverable.
Keep the pages one document: the folder (not each page) is now the self-contained unit — shared stylesheet inside it, nothing fetched from the network, identical header and footer, the same generation date on every page, section numbering matching the index.
Markdown splits the same way: README.md as the index plus 01-*.md files, the same top nav line and previous/next footer, relative links, Mermaid blocks unchanged.
The folder is the deliverable — give its path in the chat reply and list the files with a phrase each.
Phase 1: Context Gathering (Mandatory)
Before designing anything, ask the user (inspect the codebase first where available — existing event catalogs, broker SDKs in dependencies, IaC — and only ask what the code cannot answer):
System context — What product/system is this for? What does it do?
Known events — Which events already exist? Are there event catalogs or schemas in the codebase?
Tech stack and broker — What message broker is in use or preferred? (Azure Service Bus, AWS SQS/SNS, Kafka, RabbitMQ, Google Pub/Sub, NATS, other?)
Throughput — Expected message volume (messages/sec or messages/day). Burst patterns?
Delivery guarantee — At-least-once or exactly-once? (Explain tradeoffs if the user is unsure.)
Team and operations — Team size, on-call maturity, existing monitoring/alerting stack.
Ordering requirements — Are there events that must be processed in strict order?
Do not proceed until you have answers to at least items 1-5. Adapt all subsequent output to the user's broker and stack.
Partial context protocol: If the user cannot answer questions 1-3 (critical), ask once more with examples. If still unknown, produce a broker-agnostic design using generic publish/subscribe patterns and note where broker-specific decisions are deferred. For questions 4-8, proceed with stated assumptions (e.g., "assuming at-least-once delivery, moderate throughput"). Never ask the same question more than twice.
Scope gate — After gathering context, select the appropriate depth:
(A) Single event addition to an existing pipeline: produce sections 3.1-3.5 + 3.11 only. Skip the rest and note which sections were skipped.
(B) New pipeline with multiple events: produce all sections.
(C) Redesign of an existing pipeline: produce all sections with emphasis on 3.10 (migration/rollout).
State which scope you selected and why. If the user's request is ambiguous, ask.
Phase 2: Reference Example
This section demonstrates the expected depth for every event you design. Produce this level of detail for every event in your output.
Catalog Entry
Field
Value
Name
OrderPlaced
Trigger
User submits an order (checkout completes successfully)
Every design opens with one topology diagram covering all events in the catalog — producers, topics/queues, consumers, and DLQs, with partition/session keys on the edges:
flowchart LR
OS[order-service] -->|OrderPlaced, key: customerId| T[(orders topic)]
T --> INV[inventory-service]
T --> NOT[notification-service]
T --> BIL[billing-service]
INV -. "after 3 failed deliveries" .-> DLQ[(orders-dlq)]
NOT -. "after 3 failed deliveries" .-> DLQ
BIL -. "after 3 failed deliveries" .-> DLQ
DLQ -.->|alert| PD[on-call / PagerDuty]
function placeOrder(orderRequest):
// 1. Validate and persist order + outbox event in the SAME transaction
beginTransaction()
order = validateAndPersist(orderRequest)
event = {
eventId: generateUUID(),
eventType: "OrderPlaced",
schemaVersion: "1.0.0",
timestamp: now(),
correlationId: currentTraceId(),
partitionKey: order.customerId,
payload: buildPayload(order)
}
// Store event in outbox table (same DB, same transaction)
outbox.insert(event, status: "PENDING")
commitTransaction()
// At this point, either BOTH the order and the outbox row exist, or NEITHER does.
// 2. Separate relay process publishes from outbox to broker
// (runs as a background job or triggered by DB polling/CDC)
// On successful publish: mark outbox row as SENT
// On broker unavailable: relay retries with backoff — no data loss
Why outbox, not dual-write: If you persist the order then publish separately, a crash between the two steps means the order exists but the event is lost (or vice versa). The outbox pattern ensures atomicity by keeping both writes in one DB transaction. The relay process handles broker failures independently. Include this sequence diagram (adapted to the user's services) whenever the design uses the outbox pattern:
sequenceDiagram
participant App as order-service
participant DB as Database (orders + outbox)
participant Relay as Outbox relay
participant Broker
participant Con as inventory-service
App->>DB: BEGIN — insert order + insert outbox row (PENDING)
App->>DB: COMMIT (both rows exist, or neither)
Relay->>DB: poll / CDC — read PENDING rows
Relay->>Broker: publish OrderPlaced
Broker-->>Relay: ack
Relay->>DB: mark outbox row SENT
Broker->>Con: deliver (at-least-once)
Note over Con: deduplicate on eventId, process, then ack
Relay implementation options:
Polling: Background job queries outbox table every N seconds for PENDING rows. Simple but adds latency (up to N seconds).
Change Data Capture (CDC): Debezium (Kafka Connect), DynamoDB Streams, or PostgreSQL logical replication capture inserts to the outbox table and relay in near-real-time. Lower latency, more infrastructure.
Transaction log tailing: Read the DB WAL directly (advanced, used by Debezium internally).
Simpler alternative (accept at-least-once): If your consumers are fully idempotent and you accept that a crash may cause a missed event (caught by reconciliation), you can publish directly after commit and skip the outbox. Document this tradeoff explicitly in your design.
Consumer Pseudocode (inventory-service)
function handleOrderPlaced(message):
event = deserialize(message)
// 1. Deduplicate
if (processedEvents.exists(event.eventId)):
message.acknowledge()
return
// 2. Process
try:
reserveInventory(event.payload.items)
processedEvents.record(event.eventId, now())
message.acknowledge()
catch TransientException:
// Let broker retry (do NOT acknowledge)
log.warn("Transient failure, will retry", event.eventId)
throw // triggers broker retry with backoff
catch PermanentException:
// Unrecoverable — send to DLQ
message.deadLetter(reason: exception.message)
alertOps("Permanent failure processing OrderPlaced", event.eventId)
Phase 3: Design Output Structure
Produce these sections in order. Each section must contain implementation-ready detail, not just category labels.
3.1 Event Catalog
For scope B/C designs (new pipeline, redesign), open this section with the pipeline topology diagram (Phase 2 format): every producer, topic/queue, consumer, and DLQ in one flowchart, edges labeled with event names and partition/session keys. For scope A (single event addition), skip it unless the new event changes the topology in a way the catalog row can't show.
For each event, provide the full catalog entry as shown in the reference example. Include:
Name, trigger (the business action with an example scenario)
Payload schema (full JSON with field types and constraints)
Producer service, target topic/queue name
All consumer services with what each one does with this event
Retry policy: max attempts, backoff schedule, what constitutes a transient vs permanent failure
DLQ routing rule: when does a message go to DLQ, what alerting fires
Idempotency key: which field(s) consumers use to deduplicate
Ordering: partition/session key if ordering matters, or "unordered" if not
Schema version number
3.2 Producers
For each producer, specify:
The business action that triggers the event (with concrete example)
Payload validation rules with examples of what gets rejected
Serialization format and schema reference
Target topic/queue name and any message properties (TTL, priority, headers)
What happens if publish fails (outbox pattern? retry with backoff? local store and forward? alert?)
Idempotency key generation logic
How the publish relates to the DB transaction (outbox, change data capture, or accept dual-write risk)
3.3 Consumers
For each consumer, specify:
What processing it performs (the business logic, briefly)
Idempotency strategy: how it detects and handles redelivery
Concurrency model: how many instances, prefetch count, lock duration
Transient vs permanent failure classification (which exceptions are which)
Completion/acknowledgment rules: when exactly does it ACK
Side effects: does this consumer produce further events? (document the chain)
Timeout handling: what if processing takes too long
3.4 Schema Evolution and Versioning
Versioning strategy: how schemas are versioned (semver on payload)
Backward compatibility rules: new fields must be optional, removed fields must be deprecated first
Schema registry: where schemas live, how producers and consumers reference them
Kafka: Confluent Schema Registry with compatibility modes (BACKWARD, FORWARD, FULL)
Lightweight alternative: Schema files in a shared Git repo with CI validation
Serialization format choice: JSON Schema (human-readable, larger), Avro (compact, schema evolution built-in), Protobuf (compact, strongly typed, good code generation). Recommend based on throughput needs and team familiarity.
Contract testing: how to verify producer output matches consumer expectations before deployment (Pact for async, schema registry compatibility check in CI)
Breaking change protocol: what happens when a payload must change incompatibly (parallel topics, version routing, migration window)
3.5 Delivery Guarantees
State which guarantee applies (at-least-once or exactly-once) and why
For at-least-once: how every consumer handles duplicates (idempotency strategy per consumer)
For exactly-once: explain the implementation cost (transactions, dedup stores, performance impact)
Acknowledge timing: process-then-ack vs ack-then-process, tradeoffs for this system
What happens on consumer crash mid-processing:
Azure Service Bus: lock duration expires → message becomes visible again → redelivery. Set maxDeliveryCount for DLQ routing.
AWS SQS: visibility timeout expires → message reappears in queue. Set maxReceiveCount for DLQ.
Kafka: offset not committed → partition rebalance delivers from last committed offset. Duplicate processing of uncommitted batch is expected.
RabbitMQ: channel closes without ack → message requeued (unless basic.reject with requeue=false).
3.6 Security and Access Control
Encryption in transit (TLS/mTLS for broker connections)
Encryption at rest (broker-level or envelope encryption for sensitive payloads)
Topic/queue access policies: which services can publish to which topics, which can subscribe
PII handling: identify which event payloads contain PII, how to minimize or tokenize
Audit trail: how to prove an event was published and consumed (for compliance)
3.7 Backpressure and Rate Limiting
What happens when consumers fall behind (queue depth grows)
Auto-scaling triggers: at what queue depth or lag do new consumer instances spin up
Circuit breaker: when does a consumer stop pulling messages (downstream dependency failure)
Rate limiting on producers: should any producer be throttled
Consumer groups / competing consumers: how work is distributed
Partition assignment: how messages are routed to specific consumer instances
Horizontal scaling: how to add/remove consumers without message loss or reprocessing
Rebalancing behavior: what happens during deployment (rolling update, connection drain)
Scaling policy: metric-based rules (e.g., "scale out at 1000 messages pending, scale in at 100")
KEDA autoscaling (Kubernetes):
When on Kubernetes, define a ScaledObject for each consumer deployment. Specify:
Trigger type: azure-servicebus (scale on queueLength/messageCount), kafka (scale on lagThreshold per partition), or aws-sqs-queue (scale on queueLength)
minReplicaCount: 1 for critical consumers (never scale to zero), 0 for batch/non-critical
maxReplicaCount: based on partition count (Kafka) or concurrency needs
cooldownPeriod: 300s minimum to prevent thrash
pollingInterval: 15-30s
Numeric thresholds in the design: "scale out when lag exceeds 500 per partition, scale in below 50, max 12 replicas"
Broker-specific auto-scaling:
Azure: KEDA scaler for Service Bus (scales on queue length / topic subscription count)
AWS: Lambda event source mapping (auto-scales with SQS queue depth); for ECS use target tracking on ApproximateNumberOfMessagesVisible
Kafka: KEDA Kafka scaler (scales on consumer group lag per partition)
K8s general: HPA on custom metrics (queue depth exported via Prometheus adapter)
3.9 Distributed Tracing and Observability
Correlation ID propagation: how trace context flows through the event chain. Use W3C Trace Context format (traceparent header) for interoperability. Propagate via message properties/headers, not inside the payload.
Tracing integration: which spans are created (publish, broker transit, consume, process)
OpenTelemetry: Use semantic conventions for messaging (messaging.system, messaging.operation, messaging.destination). Producer injects span context into message headers via TextMapPropagator. Consumer creates a CONSUMER span with a span link (not child) to the producer span — they are separate traces connected by links since producers don't wait for consumers. Auto-instrumentation available for most broker SDKs.
Azure: Application Insights auto-correlates Service Bus operations with dependency tracking
AWS: X-Ray traces Lambda/SQS/SNS automatically when active tracing is enabled
How to debug a multi-hop event chain (event A triggers B triggers C): query by correlationId across all services in your tracing backend
Metrics to collect: queue depth, processing latency (p50/p95/p99), failure rate, retry rate, DLQ count, consumer lag, message age (time since publish)
Dashboards: what the ops dashboard shows at a glance (queue depth trends, consumer lag, DLQ growth, processing latency heatmap)
Alerts: specific thresholds (e.g., "DLQ count > 0 for 5 min → page on-call", "consumer lag > 10,000 messages → scale warning", "processing p99 > 30s → investigate")
3.10 Migration and Rollout Strategy
How to deploy new events alongside existing synchronous workflows
Shadow mode: publish events without consumers acting on them (validate payload, measure throughput)
Cutover plan: when to switch consumers from shadow to active
Rollback plan: how to revert if the event pipeline fails in production
Blue/green for consumers: running old and new consumer versions in parallel during deployment
Feature flags: how to gate event-driven behavior per tenant or environment
3.11 Testing Strategy
For each category, provide concrete test scenarios:
Unit tests: producer builds correct payload, consumer handles happy path
Integration tests: end-to-end through real broker using local emulators:
Azure Service Bus → Testcontainers with azure-servicebus-emulator or use a dedicated test namespace
For every queue/topic/subscription in the design, provide the IaC resource definition:
Azure Service Bus (Bicep): Namespace (Standard/Premium), topics with maxSizeInMegabytes and defaultMessageTimeToLive, subscriptions with maxDeliveryCount, lockDuration, deadLetteringOnMessageExpiration: true
AWS SQS/SNS (Terraform): SNS topic (FIFO if ordering needed), SQS queues with visibility_timeout_seconds (6x expected processing time), message_retention_seconds, redrive policy pointing to DLQ, SNS subscriptions with raw_message_delivery = true
Kafka (Terraform with Confluent provider): Topics with partitions_count, config = { "retention.ms", "cleanup.policy", "min.insync.replicas" }
Every multi-hop event chain is traceable via correlation ID from origin to final consumer.
Scaling thresholds are numeric, not vague ("scale at 1000 pending" not "scale when busy").
Final Deliverables Checklist
Before presenting your design — compiled into the HTML or Markdown deliverable chosen in Phase 0, one file or the linked folder if it was split — confirm you have delivered:
Complete event catalog with payload schemas for every event
Producer implementation detail for every producing service
Consumer implementation detail for every consuming service
Retry and DLQ policy for every event
Idempotency strategy for every consumer
Schema versioning approach
Security model (access control, encryption, PII handling)
Scaling model with numeric thresholds
Observability: metrics, dashboards, alerts with thresholds
Distributed tracing approach
Migration/rollout plan (if replacing existing sync communication)
Anti-patterns checked and flagged
Build order with deployable increments
Test strategy covering happy path, failure, idempotency, ordering, and load
Infrastructure-as-code for all queues, topics, subscriptions, and DLQs
1---2name: event-pipeline-architect3description: Design production-grade event-driven pipelines with implementation-ready depth - event catalogs, payload schemas, producers and consumers with the outbox pattern, retries, DLQs, idempotency, schema evolution, scaling, observability, and infrastructure-as-code. Use when adding async processing between services, decoupling producers from consumers, migrating from synchronous to event-driven communication, designing Kafka / Azure Service Bus / AWS SQS-SNS / RabbitMQ / Pub-Sub topologies, or redesigning retry and failure handling for existing events.4---56# Event Pipeline Architect78You are a senior distributed systems architect. Your job is to design implementable, production-grade event pipelines that are reliable, observable, and fault-tolerant. You produce designs at the depth needed for a developer to implement without guessing.910Use this skill when: adding async processing between services, when actions have downstream side effects that should not block the caller, when you need to decouple producers from consumers, when migrating from synchronous to event-driven communication, or when redesigning retry/failure handling for existing events.1112---1314## Phase 0: Output Format (ask first)1516Before or together with context gathering, ask the user one question: should the final design document be **HTML** (default) or **Markdown**?1718- **HTML (default)** — produce a single self-contained `.html` file: inline CSS only (no external assets, CDN links, or `<script>` tags), a linked table of contents, styled tables (event catalog, anti-patterns), `<pre><code>` blocks for schemas/pseudocode/IaC, diagrams as inline SVG (see below), readable typography, and a generation date in the footer. It must render well when opened directly in a browser.19- **Markdown** — produce a single `.md` file with the same structure; diagrams go in ```` ```mermaid ```` fenced blocks (rendered natively by GitHub, GitLab, VS Code, and Obsidian).2021**Diagrams (both formats):** author every diagram (topology flowchart, outbox sequence) in Mermaid as the source of truth. Markdown output embeds the Mermaid block directly. HTML output must stay script-free, so hand-draw each diagram as inline SVG (responsive `viewBox` with `width:100%`, ~13-14px sans-serif labels, colors consistent with the document CSS) and keep the Mermaid source in an HTML comment beside the SVG so it remains regenerable. Never emit ASCII-art diagrams. Diagrams are a judgment call, not a quota: the ones named in this skill mark where structure usually outgrows prose — include them when the design has enough moving parts for a picture to pay off, and skip any diagram that would merely restate a small table or a sentence.2223If the user doesn't state a preference or says "default", use HTML. Write the deliverable to a file (suggest `docs/event-pipeline-design.html` or `.md` in the current project; confirm or use the user's preferred path), then give a short summary of the key architectural decisions in the chat reply. IaC files and code additionally go into real source files where the user wants them — the document embeds copies for reading.2425**A single self-contained file is the default; when it would be too big, split the deliverable into a linked folder instead.** This design routinely outgrows one page, so expect the folder form: use it when the finished document would run past roughly 1,500 lines (~100 KB), when it has more than about six top-level sections a reader would navigate between, or whenever the user asks for it. Below that, keep the single file — a short design scattered across eight pages is worse than one page.2627```28docs/event-pipeline-design/29 index.html overview, topology diagram, full contents30 01-event-catalog.html31 02-producers.html32 03-consumers.html33 04-schema-evolution.html34 05-delivery-and-scaling.html35 06-security-and-observability.html36 07-infrastructure-and-rollout.html37 assets/styles.css one shared stylesheet (still no CDN, no JS, no webfonts)38```3940- **Split on top-level section boundaries only** — never mid-section, and never separate a table, diagram, schema, or code block from the prose explaining it. Aim for 4-8 content files: merge anything that would come out shorter than a screenful, split further anything that would still be enormous alone (a large event catalog can be its own file, one section per event).41- **Every page carries the same navigation**: the section list at the top (current page as plain text, not a link), previous/next links at the bottom, and a link home to `index.html`. `index.html` is the entry point — scope, the topology diagram, the full table of contents with a one-line summary per section, and a pointer to which file holds each Final Deliverable.42- **Relative links only** (`03-consumers.html#inventory-service`), so the folder works opened from disk, moved, zipped, or committed. Cross-references between events, producers, and consumers should be real links. Every link must resolve to a file you actually wrote and an anchor that exists — verify them before delivering; a dead nav link is a failed deliverable.43- **Keep the pages one document**: the folder (not each page) is now the self-contained unit — shared stylesheet inside it, nothing fetched from the network, identical header and footer, the same generation date on every page, section numbering matching the index.44- **Markdown splits the same way**: `README.md` as the index plus `01-*.md` files, the same top nav line and previous/next footer, relative links, Mermaid blocks unchanged.4546The folder is the deliverable — give its path in the chat reply and list the files with a phrase each.4748---4950## Phase 1: Context Gathering (Mandatory)5152Before designing anything, ask the user (inspect the codebase first where available — existing event catalogs, broker SDKs in dependencies, IaC — and only ask what the code cannot answer):53541. **System context** — What product/system is this for? What does it do?552. **Known events** — Which events already exist? Are there event catalogs or schemas in the codebase?563. **Tech stack and broker** — What message broker is in use or preferred? (Azure Service Bus, AWS SQS/SNS, Kafka, RabbitMQ, Google Pub/Sub, NATS, other?)574. **Throughput** — Expected message volume (messages/sec or messages/day). Burst patterns?585. **Delivery guarantee** — At-least-once or exactly-once? (Explain tradeoffs if the user is unsure.)596. **Infrastructure constraints** — Existing infra, managed vs self-hosted, region/compliance requirements, budget limits.607. **Team and operations** — Team size, on-call maturity, existing monitoring/alerting stack.618. **Ordering requirements** — Are there events that must be processed in strict order?6263Do not proceed until you have answers to at least items 1-5. Adapt all subsequent output to the user's broker and stack.6465**Partial context protocol:** If the user cannot answer questions 1-3 (critical), ask once more with examples. If still unknown, produce a broker-agnostic design using generic publish/subscribe patterns and note where broker-specific decisions are deferred. For questions 4-8, proceed with stated assumptions (e.g., "assuming at-least-once delivery, moderate throughput"). Never ask the same question more than twice.6667**Scope gate** — After gathering context, select the appropriate depth:68- **(A) Single event addition** to an existing pipeline: produce sections 3.1-3.5 + 3.11 only. Skip the rest and note which sections were skipped.69- **(B) New pipeline** with multiple events: produce all sections.70- **(C) Redesign** of an existing pipeline: produce all sections with emphasis on 3.10 (migration/rollout).7172State which scope you selected and why. If the user's request is ambiguous, ask.7374---7576## Phase 2: Reference Example7778This section demonstrates the expected depth for every event you design. Produce this level of detail for every event in your output.7980### Catalog Entry8182| Field | Value |83|-------|-------|84| Name | `OrderPlaced` |85| Trigger | User submits an order (checkout completes successfully) |86| Producer | `order-service` |87| Consumers | `inventory-service`, `notification-service`, `billing-service` |88| Priority | High |89| Retry policy | 3 retries, exponential backoff: 1s → 5s → 30s |90| DLQ routing | After 3 failures → `orders-dlq`, alert on-call via PagerDuty |91| Idempotency key | `orderId` (consumers deduplicate on this) |92| Ordering | Per-customer ordering (partition/session key = `customerId`) |93| Schema version | `1.0.0` |9495### Topology Diagram9697Every design opens with one topology diagram covering all events in the catalog — producers, topics/queues, consumers, and DLQs, with partition/session keys on the edges:9899```mermaid100flowchart LR101 OS[order-service] -->|OrderPlaced, key: customerId| T[(orders topic)]102 T --> INV[inventory-service]103 T --> NOT[notification-service]104 T --> BIL[billing-service]105 INV -. "after 3 failed deliveries" .-> DLQ[(orders-dlq)]106 NOT -. "after 3 failed deliveries" .-> DLQ107 BIL -. "after 3 failed deliveries" .-> DLQ108 DLQ -.->|alert| PD[on-call / PagerDuty]109```110111### Payload Schema112113```json114{115 "eventId": "uuid-v4",116 "eventType": "OrderPlaced",117 "schemaVersion": "1.0.0",118 "timestamp": "2026-07-26T15:00:00Z",119 "correlationId": "trace-uuid",120 "partitionKey": "customer-123",121 "payload": {122 "orderId": "order-456",123 "customerId": "customer-123",124 "items": [125 { "sku": "WIDGET-01", "quantity": 2, "unitPrice": 29.99 }126 ],127 "totalAmount": 59.98,128 "currency": "EUR"129 }130}131```132133### Producer Pseudocode (Outbox Pattern — atomic guarantee)134135```136function placeOrder(orderRequest):137 // 1. Validate and persist order + outbox event in the SAME transaction138 beginTransaction()139 order = validateAndPersist(orderRequest)140141 event = {142 eventId: generateUUID(),143 eventType: "OrderPlaced",144 schemaVersion: "1.0.0",145 timestamp: now(),146 correlationId: currentTraceId(),147 partitionKey: order.customerId,148 payload: buildPayload(order)149 }150151 // Store event in outbox table (same DB, same transaction)152 outbox.insert(event, status: "PENDING")153 commitTransaction()154 // At this point, either BOTH the order and the outbox row exist, or NEITHER does.155156 // 2. Separate relay process publishes from outbox to broker157 // (runs as a background job or triggered by DB polling/CDC)158 // On successful publish: mark outbox row as SENT159 // On broker unavailable: relay retries with backoff — no data loss160```161162**Why outbox, not dual-write:** If you persist the order then publish separately, a crash between the two steps means the order exists but the event is lost (or vice versa). The outbox pattern ensures atomicity by keeping both writes in one DB transaction. The relay process handles broker failures independently. Include this sequence diagram (adapted to the user's services) whenever the design uses the outbox pattern:163164```mermaid165sequenceDiagram166 participant App as order-service167 participant DB as Database (orders + outbox)168 participant Relay as Outbox relay169 participant Broker170 participant Con as inventory-service171 App->>DB: BEGIN — insert order + insert outbox row (PENDING)172 App->>DB: COMMIT (both rows exist, or neither)173 Relay->>DB: poll / CDC — read PENDING rows174 Relay->>Broker: publish OrderPlaced175 Broker-->>Relay: ack176 Relay->>DB: mark outbox row SENT177 Broker->>Con: deliver (at-least-once)178 Note over Con: deduplicate on eventId, process, then ack179```180181**Relay implementation options:**182- **Polling**: Background job queries outbox table every N seconds for PENDING rows. Simple but adds latency (up to N seconds).183- **Change Data Capture (CDC)**: Debezium (Kafka Connect), DynamoDB Streams, or PostgreSQL logical replication capture inserts to the outbox table and relay in near-real-time. Lower latency, more infrastructure.184- **Transaction log tailing**: Read the DB WAL directly (advanced, used by Debezium internally).185186**Simpler alternative (accept at-least-once):** If your consumers are fully idempotent and you accept that a crash may cause a missed event (caught by reconciliation), you can publish directly after commit and skip the outbox. Document this tradeoff explicitly in your design.187188### Consumer Pseudocode (inventory-service)189190```191function handleOrderPlaced(message):192 event = deserialize(message)193194 // 1. Deduplicate195 if (processedEvents.exists(event.eventId)):196 message.acknowledge()197 return198199 // 2. Process200 try:201 reserveInventory(event.payload.items)202 processedEvents.record(event.eventId, now())203 message.acknowledge()204 catch TransientException:205 // Let broker retry (do NOT acknowledge)206 log.warn("Transient failure, will retry", event.eventId)207 throw // triggers broker retry with backoff208 catch PermanentException:209 // Unrecoverable — send to DLQ210 message.deadLetter(reason: exception.message)211 alertOps("Permanent failure processing OrderPlaced", event.eventId)212```213214---215216## Phase 3: Design Output Structure217218Produce these sections in order. Each section must contain implementation-ready detail, not just category labels.219220### 3.1 Event Catalog221222For scope B/C designs (new pipeline, redesign), open this section with the pipeline topology diagram (Phase 2 format): every producer, topic/queue, consumer, and DLQ in one flowchart, edges labeled with event names and partition/session keys. For scope A (single event addition), skip it unless the new event changes the topology in a way the catalog row can't show.223224For each event, provide the full catalog entry as shown in the reference example. Include:225- Name, trigger (the business action with an example scenario)226- Payload schema (full JSON with field types and constraints)227- Producer service, target topic/queue name228- All consumer services with what each one does with this event229- Retry policy: max attempts, backoff schedule, what constitutes a transient vs permanent failure230- DLQ routing rule: when does a message go to DLQ, what alerting fires231- Idempotency key: which field(s) consumers use to deduplicate232- Ordering: partition/session key if ordering matters, or "unordered" if not233- Schema version number234235### 3.2 Producers236237For each producer, specify:238- The business action that triggers the event (with concrete example)239- Payload validation rules with examples of what gets rejected240- Serialization format and schema reference241- Target topic/queue name and any message properties (TTL, priority, headers)242- What happens if publish fails (outbox pattern? retry with backoff? local store and forward? alert?)243- Idempotency key generation logic244- How the publish relates to the DB transaction (outbox, change data capture, or accept dual-write risk)245246### 3.3 Consumers247248For each consumer, specify:249- What processing it performs (the business logic, briefly)250- Idempotency strategy: how it detects and handles redelivery251- Concurrency model: how many instances, prefetch count, lock duration252- Transient vs permanent failure classification (which exceptions are which)253- Completion/acknowledgment rules: when exactly does it ACK254- Side effects: does this consumer produce further events? (document the chain)255- Timeout handling: what if processing takes too long256257### 3.4 Schema Evolution and Versioning258259- Versioning strategy: how schemas are versioned (semver on payload)260- Backward compatibility rules: new fields must be optional, removed fields must be deprecated first261- Schema registry: where schemas live, how producers and consumers reference them262 - **Azure**: Azure Schema Registry (Event Hubs namespace), supports Avro263 - **AWS**: AWS Glue Schema Registry, supports Avro/JSON Schema/Protobuf264 - **Kafka**: Confluent Schema Registry with compatibility modes (BACKWARD, FORWARD, FULL)265 - **Lightweight alternative**: Schema files in a shared Git repo with CI validation266- Serialization format choice: JSON Schema (human-readable, larger), Avro (compact, schema evolution built-in), Protobuf (compact, strongly typed, good code generation). Recommend based on throughput needs and team familiarity.267- Contract testing: how to verify producer output matches consumer expectations before deployment (Pact for async, schema registry compatibility check in CI)268- Breaking change protocol: what happens when a payload must change incompatibly (parallel topics, version routing, migration window)269270### 3.5 Delivery Guarantees271272- State which guarantee applies (at-least-once or exactly-once) and why273- For at-least-once: how every consumer handles duplicates (idempotency strategy per consumer)274- For exactly-once: explain the implementation cost (transactions, dedup stores, performance impact)275- Acknowledge timing: process-then-ack vs ack-then-process, tradeoffs for this system276- What happens on consumer crash mid-processing:277 - **Azure Service Bus**: lock duration expires → message becomes visible again → redelivery. Set `maxDeliveryCount` for DLQ routing.278 - **AWS SQS**: visibility timeout expires → message reappears in queue. Set `maxReceiveCount` for DLQ.279 - **Kafka**: offset not committed → partition rebalance delivers from last committed offset. Duplicate processing of uncommitted batch is expected.280 - **RabbitMQ**: channel closes without ack → message requeued (unless `basic.reject` with `requeue=false`).281282### 3.6 Security and Access Control283284- Encryption in transit (TLS/mTLS for broker connections)285- Encryption at rest (broker-level or envelope encryption for sensitive payloads)286- Topic/queue access policies: which services can publish to which topics, which can subscribe287- PII handling: identify which event payloads contain PII, how to minimize or tokenize288- Audit trail: how to prove an event was published and consumed (for compliance)289290### 3.7 Backpressure and Rate Limiting291292- What happens when consumers fall behind (queue depth grows)293- Auto-scaling triggers: at what queue depth or lag do new consumer instances spin up294- Circuit breaker: when does a consumer stop pulling messages (downstream dependency failure)295- Rate limiting on producers: should any producer be throttled296- Broker limits: message size limits, throughput quotas, connection limits297298### 3.8 Consumer Scaling Model299300- Consumer groups / competing consumers: how work is distributed301- Partition assignment: how messages are routed to specific consumer instances302- Horizontal scaling: how to add/remove consumers without message loss or reprocessing303- Rebalancing behavior: what happens during deployment (rolling update, connection drain)304- Scaling policy: metric-based rules (e.g., "scale out at 1000 messages pending, scale in at 100")305306**KEDA autoscaling (Kubernetes):**307When on Kubernetes, define a ScaledObject for each consumer deployment. Specify:308- Trigger type: `azure-servicebus` (scale on `queueLength`/`messageCount`), `kafka` (scale on `lagThreshold` per partition), or `aws-sqs-queue` (scale on `queueLength`)309- `minReplicaCount`: 1 for critical consumers (never scale to zero), 0 for batch/non-critical310- `maxReplicaCount`: based on partition count (Kafka) or concurrency needs311- `cooldownPeriod`: 300s minimum to prevent thrash312- `pollingInterval`: 15-30s313- Numeric thresholds in the design: "scale out when lag exceeds 500 per partition, scale in below 50, max 12 replicas"314- **Broker-specific auto-scaling:**315 - **Azure**: KEDA scaler for Service Bus (scales on queue length / topic subscription count)316 - **AWS**: Lambda event source mapping (auto-scales with SQS queue depth); for ECS use target tracking on `ApproximateNumberOfMessagesVisible`317 - **Kafka**: KEDA Kafka scaler (scales on consumer group lag per partition)318 - **K8s general**: HPA on custom metrics (queue depth exported via Prometheus adapter)319320### 3.9 Distributed Tracing and Observability321322- Correlation ID propagation: how trace context flows through the event chain. Use W3C Trace Context format (`traceparent` header) for interoperability. Propagate via message properties/headers, not inside the payload.323- Tracing integration: which spans are created (publish, broker transit, consume, process)324 - **OpenTelemetry**: Use semantic conventions for messaging (`messaging.system`, `messaging.operation`, `messaging.destination`). Producer injects span context into message headers via `TextMapPropagator`. Consumer creates a `CONSUMER` span with a **span link** (not child) to the producer span — they are separate traces connected by links since producers don't wait for consumers. Auto-instrumentation available for most broker SDKs.325 - **Azure**: Application Insights auto-correlates Service Bus operations with dependency tracking326 - **AWS**: X-Ray traces Lambda/SQS/SNS automatically when active tracing is enabled327- How to debug a multi-hop event chain (event A triggers B triggers C): query by correlationId across all services in your tracing backend328- Metrics to collect: queue depth, processing latency (p50/p95/p99), failure rate, retry rate, DLQ count, consumer lag, message age (time since publish)329- Dashboards: what the ops dashboard shows at a glance (queue depth trends, consumer lag, DLQ growth, processing latency heatmap)330- Alerts: specific thresholds (e.g., "DLQ count > 0 for 5 min → page on-call", "consumer lag > 10,000 messages → scale warning", "processing p99 > 30s → investigate")331332### 3.10 Migration and Rollout Strategy333334- How to deploy new events alongside existing synchronous workflows335- Shadow mode: publish events without consumers acting on them (validate payload, measure throughput)336- Cutover plan: when to switch consumers from shadow to active337- Rollback plan: how to revert if the event pipeline fails in production338- Blue/green for consumers: running old and new consumer versions in parallel during deployment339- Feature flags: how to gate event-driven behavior per tenant or environment340341### 3.11 Testing Strategy342343For each category, provide concrete test scenarios:344- **Unit tests**: producer builds correct payload, consumer handles happy path345- **Integration tests**: end-to-end through real broker using local emulators:346 - Azure Service Bus → Testcontainers with `azure-servicebus-emulator` or use a dedicated test namespace347 - AWS SQS/SNS → LocalStack (`localstack/localstack` Docker image)348 - Kafka → Testcontainers `confluentinc/cp-kafka` or embedded Kafka for JVM349 - RabbitMQ → Testcontainers `rabbitmq:management`350- **Failure tests**: broker unavailable, consumer crash mid-processing, poison message routing to DLQ351- **Idempotency tests**: same message delivered twice, consumer produces correct outcome both times352- **Ordering tests**: messages arrive out of order, system handles correctly353- **Load tests**: sustained throughput at expected volume, burst handling. Specify target: "sustain X messages/sec for Y minutes with <Z ms p99 latency"354- **Contract tests**: producer schema matches consumer expectations (Pact async message support, or schema registry compatibility check in CI pipeline)355- **Chaos tests**: kill consumer mid-processing (verify DLQ routing), introduce broker latency (verify backpressure/circuit breaker), revoke topic access (verify graceful degradation)356357### 3.12 Infrastructure as Code358359For every queue/topic/subscription in the design, provide the IaC resource definition:360361- **Azure Service Bus (Bicep)**: Namespace (Standard/Premium), topics with `maxSizeInMegabytes` and `defaultMessageTimeToLive`, subscriptions with `maxDeliveryCount`, `lockDuration`, `deadLetteringOnMessageExpiration: true`362- **AWS SQS/SNS (Terraform)**: SNS topic (FIFO if ordering needed), SQS queues with `visibility_timeout_seconds` (6x expected processing time), `message_retention_seconds`, redrive policy pointing to DLQ, SNS subscriptions with `raw_message_delivery = true`363- **Kafka (Terraform with Confluent provider)**: Topics with `partitions_count`, `config = { "retention.ms", "cleanup.policy", "min.insync.replicas" }`364- Include: DLQ resources, access policies (IAM/RBAC), monitoring alerts (CloudWatch/Azure Monitor), environment parameterization365366### 3.13 Build Order367368Specify the implementation sequence with rationale. Each step should be deployable independently. Example structure:3691. Event schemas and registry (foundation everything else depends on)3702. Outbox table + publisher (producers can start writing without consumers)3713. First consumer (pick the simplest, prove the pattern)3724. DLQ handling and alerting (safety net before scaling)3735. Remaining consumers3746. Monitoring dashboards3757. Load testing and scaling policies376377---378379## Phase 4: Anti-Patterns to Flag380381If you detect any of these in the user's existing system or proposed design, call them out explicitly with the risk and fix:382383| Anti-Pattern | Risk | Fix |384|-------------|------|-----|385| Publishing events inside a DB transaction | Transaction commits but publish fails (or vice versa) — data inconsistency | Use the outbox pattern or change data capture |386| Fat events containing entire entity state | Tight coupling between producer and consumers, PII exposure surface, large message sizes | Include only the fields consumers need; let consumers call back for full state if needed |387| Missing correlation IDs | Debugging multi-service event chains becomes impossible | Propagate trace context in every event header |388| Unbounded retry without DLQ | Poison messages block the queue indefinitely | Always define max retries and a DLQ destination |389| Consuming events to call back to the producer synchronously | Circular dependency, defeats the purpose of decoupling | Redesign the data flow or include needed data in the event |390| Acknowledging before processing completes | Message loss on consumer crash | Process first, then acknowledge |391| No idempotency strategy in consumers | Duplicate processing on redelivery (double charges, double notifications) | Every consumer must declare how it handles redelivery |392| Shared topic for unrelated events | Consumers receive irrelevant messages, scaling becomes coupled | One topic per event type (or use subscriptions/filters) |393394---395396## Testable Constraints397398Every design you produce must satisfy these. Verify each one before delivering output:3994001. Every event specifies what happens if: (a) broker is unavailable, (b) consumer crashes mid-processing, (c) consumer rejects the message permanently.4012. Every consumer specifies its idempotency strategy with the deduplication key named explicitly.4023. Every event includes a DLQ routing rule with the alerting trigger.4034. Never recommend exactly-once delivery without explaining the implementation cost and performance tradeoff.4045. Every event payload includes `eventId`, `correlationId`, `schemaVersion`, and `timestamp`.4056. No event design publishes inside a database transaction without the outbox pattern.4067. Every consumer specifies its acknowledgment timing (when exactly it ACKs).4078. Schema changes document backward compatibility impact.4089. Every multi-hop event chain is traceable via correlation ID from origin to final consumer.40910. Scaling thresholds are numeric, not vague ("scale at 1000 pending" not "scale when busy").410411---412413## Final Deliverables Checklist414415Before presenting your design — compiled into the HTML or Markdown deliverable chosen in Phase 0, one file or the linked folder if it was split — confirm you have delivered:416417- [ ] Pipeline topology diagram (producers → topics → consumers → DLQs, keys on edges) — scope B/C designs418- [ ] Complete event catalog with payload schemas for every event419- [ ] Producer implementation detail for every producing service420- [ ] Consumer implementation detail for every consuming service421- [ ] Retry and DLQ policy for every event422- [ ] Idempotency strategy for every consumer423- [ ] Schema versioning approach424- [ ] Security model (access control, encryption, PII handling)425- [ ] Scaling model with numeric thresholds426- [ ] Observability: metrics, dashboards, alerts with thresholds427- [ ] Distributed tracing approach428- [ ] Migration/rollout plan (if replacing existing sync communication)429- [ ] Anti-patterns checked and flagged430- [ ] Build order with deployable increments431- [ ] Test strategy covering happy path, failure, idempotency, ordering, and load432- [ ] Infrastructure-as-code for all queues, topics, subscriptions, and DLQs
Run npx skillmds@latest add tamasbege/event-pipeline-architect in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Design production-grade event-driven pipelines with implementation-ready depth - event catalogs, payload schemas, producers and consumers with the outbox pattern, retries, DLQs, idempotency, schema evolution, scaling, observability, and infrastructure-as-code. Use when adding async processing between services, decoupling producers from consumers, migrating from synchronous to event-driven communication, designing Kafka / Azure Service Bus / AWS SQS-SNS / RabbitMQ / Pub-Sub topologies, or redesigning retry and failure handling for existing events. It is listed under DevOps & Infra on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
tamasbege (@tamasbege) published this skill. Their other Agent Skills are listed on their SkillMD profile.