Event-Driven Architecture for Business Automation
Overview
Event-driven design decouples producers and consumers—critical when CRM, billing, and messaging evolve independently.
Quick definition
Event-driven automation uses durable topics or logs with at-least-once delivery, idempotent consumers, and explicit schemas—decoupling producers from workflow side effects.
Definition
Events are facts: LeadCreated, InvoicePaid, TicketEscalated. Automation reacts asynchronously with retries and ordering where needed.
Why it matters
Synchronous chains fail together; events isolate blast radius and enable replay for recovery.
Core framework
Step-by-step model as TypeScript interfaces (machine-readable checkpoints).
Idempotent consumers
/**
* Idempotent consumers
* Same event delivered twice should not double-effect.
*/
export interface CoreFrameworkStep1IdempotentConsumers {
/** Order in the core framework (0-based) */
readonly stepIndex: 0;
/** Display title for this step */
readonly title: "Idempotent consumers";
/** Narrative checkpoints as published in the guide */
readonly narrative: readonly string[];
}
export const CoreFrameworkStep1IdempotentConsumers_NARRATIVE: readonly string[] = [
"Same event delivered twice should not double-effect."
] as const;Dead-letter queues
/**
* Dead-letter queues
* Poison messages go somewhere humans can inspect.
*/
export interface CoreFrameworkStep2DeadLetterQueues {
/** Order in the core framework (0-based) */
readonly stepIndex: 1;
/** Display title for this step */
readonly title: "Dead-letter queues";
/** Narrative checkpoints as published in the guide */
readonly narrative: readonly string[];
}
export const CoreFrameworkStep2DeadLetterQueues_NARRATIVE: readonly string[] = [
"Poison messages go somewhere humans can inspect."
] as const;Detailed breakdown
Logic sections encoded as Python functions with structured narrative payloads.
Ordering
def logic_block_1_ordering(context: dict) -> dict:
"""Operational logic: Ordering"""
# Narrative steps from the guide (logic section)
paragraphs = ["When order matters, partition by business key—not global ordering."]
return {
"heading": "Ordering",
"paragraphs": paragraphs,
"context_keys": tuple(sorted(context.keys())),
}Technical patterns
Outbox pattern
- Business transaction writes domain row + outbox row atomically.
- Relay publishes to broker; marks outbox `sent`—never lose events on crash.
Consumer idempotency
- Natural key `event_id` dedupe table per consumer group.
- Retries safe: same outcome on redelivery.
Code examples
Idempotent consumer
Skips duplicate deliveries from Kafka/SQS.
export async function handleMessage(msg) {
const ok = await db.insertIgnore('processed_events', {
id: msg.id,
consumer: 'workflow-worker',
});
if (!ok) return; // duplicate
await applyDomainLogic(msg.payload);
}Outbox relay loop
Polls unsent rows and publishes.
export async function relayOutbox() {
const batch = await db.query('SELECT * FROM outbox WHERE sent_at IS NULL LIMIT 100');
for (const row of batch) {
await broker.publish(row.topic, row.payload);
await db.update('outbox', { id: row.id }, { sent_at: new Date() });
}
}System architecture
[Producer service: DB txn]
→ [Outbox table]
→ [Relay → message broker]
→ [Consumers: idempotent handlers]
→ [Workflow / projections / CRM]Real-world example
An e-commerce operator replayed a day of order events after a partial outage—reconciling CRM and warehouse without manual CSVs.
Common mistakes
- Chatty synchronous calls from UI to five APIs—fragile and slow.
- No schema versioning on events—consumers break silently.
Related topics
PrimeAxiom builds event-driven automation back ends—book an architecture whiteboard session.