event-driven-architecture
Skill0 starsChanged 33 days agoMITView on GitHub
Designing and implementing event-driven systems: defines versioned event schemas with a registry, separates domain events from integration events, implements the transactional outbox pattern to guarantee event publication, designs idempotent consumers, handles event ordering and out-of-order delivery, and avoids the coupling and observability traps that make event-driven systems hard to debug. Use when you want to design an event-driven system, publish domain events, implement the outbox pattern, decouple services with events, version an event schema, design a consumer, handle event ordering, or use Kafka or a message broker correctly.
13 other projects use a version of this.
Adopted by
Most recently taken up by
- reddygopalkrishna/developer-agent-skillstheir own version, first seen Nov 8, 2026
- delorenj/skillstheir own version, first seen Nov 7, 2026
- troskater/flowdeck-claudetheir own version, first seen Oct 25, 2026
- nimadorostkar/Claude-Skills-collectiontheir own version, first seen Oct 13, 2026
- kimtth/agent-skill-100-lines-or-lesstheir own version, first seen Jun 26, 2026
- agent-packs/registrytheir own version, first seen Jun 15, 2026
Used with
Projects with it also use performance-profiling.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
conorluddySkillperformance-profiling
Instruments integration and performance analysis workflows for iOS apps. Use when profiling CPU usage, memory allocation, network activity, or energy consumption. Covers Time Profiler, Allocations, Leaks, Network instruments, and performance optimization strategies.
Also in 3 projects that use this
Other versions13 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 13
- travisjneuman/.claude★ 106same name
- nimadorostkar/Claude-Skills-collection★ 33same name
- delorenj/skills★ 17same name
- rade-nugroho/Agentic-Enteprises-Skill★ 9same name
- daemon-blockint-tech/Agentic-Enteprises-Skill★ 8same name
- reddygopalkrishna/developer-agent-skills★ 8same name
- ffsshhttiikk/opencode-agents-skills★ 2same name
- kimtth/agent-skill-100-lines-or-less★ 2same name
- codecharmhq/claude-code-skills★ 1same name
- mmondora/claude-skills★ 1same name
3 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository68 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .claude/CLAUDE.mdCLAUDE.md
- accessibilitySkill
- api-designSkill
- api-versioningSkill
- authSkill
- background-jobsSkill
- cachingSkill
- capacity-planningSkill
- changelogSkill
- chaos-engineeringSkill
- ci-cdSkill
- circuit-breakerSkill
- claude-best-practicesSkill
- code-commentsSkill
- code-hygieneSkill
- code-reviewSkill
- code-structureSkill
- commit-styleSkill
- concurrency-and-lockingSkill
- database-transactionsSkill
- data-consistencySkill
- debug-infra-startupSkill
- debug-sessionSkill
- demo-video-creatorSkill
- demo-video-script-creatorSkill
- dependency-injectionSkill
- dependency-updateSkill
- disk-cleanupSkill
- distributed-tracingSkill
- dockerSkill
- env-configSkill
- env-setupSkill
- error-handlingSkill
- feature-flagsSkill
- file-storageSkill
- graceful-shutdownSkill
- idempotencySkill
- input-validationSkill
- jars-engineering-blogsmithSkill
- jars-workshop-builderSkill
- licensingSkill
- loggingSkill
- memory-and-allocationsSkill
- migrate-dbSkill
- monitoringSkill
- multi-tenancySkill
- notificationsSkill
- openapiSkill
- paginationSkill
- perf-profileSkill
- pr-descriptionSkill
- query-optimizationSkill
- rate-limitingSkill
- refactorSkill
- releaseSkill
- runbook-writerSkill
- searchSkill
- secrets-auditSkill
- simplifySkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
3 rules. 0 are common, 2 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 13 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 25
Every integration event has:
- Distinctive: line 332
Use the transactional outbox — never publish after a DB commit without it.
- Very specific: line 334
Every consumer must deduplicate on event_id.
“Common” is measured: kept by at least 6 in 10 of the 13 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
event-driven-architecture
Design the event contract first. Everything else — producers, consumers, brokers — is plumbing.
1. Two kinds of events — keep them separate
| Kind | What it is | Who owns it | Example |
| Domain event | Something that happened inside a bounded context | The service that owns the domain | OrderPlaced, PaymentFailed |
| Integration event | A domain event published across service boundaries | The publishing service | order.placed.v1, payment.failed.v1 |
Domain events live inside a service. Integration events cross the wire and are part of a public contract. Version integration events from day one — once a consumer depends on the schema, changing it without versioning is a breaking change.
2. Event schema — define it before writing a producer or consumer
Every integration event has:
- A stable type string (e.g.
order.placed.v1) - A version embedded in the type or as a field
- An event_id for deduplication
- A occurred_at timestamp (ISO 8601)
- A payload with the relevant domain data
# lib/events.py
"""
lib/events.py
-------------
Base event schema and registry for all integration events.
Producers and consumers import event classes from here — never define schemas inline.
Depends on: pydantic
Used by: services/*, workers/*
"""
from datetime import datetime, timezone
from uuid import uuid4
from pydantic import BaseModel, Field
class IntegrationEvent(BaseModel):
event_id: str = Field(default_factory=lambda: str(uuid4()))
occurred_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
event_type: str # set by subclass
model_config = {"frozen": True} # events are immutable
class OrderPlacedV1(IntegrationEvent):
event_type: str = "order.placed.v1"
order_id: int
tenant_id: str
user_id: int
total_cents: int
currency: str
class PaymentFailedV1(IntegrationEvent):
event_type: str = "payment.failed.v1"
order_id: int
tenant_id: str
failure_code: str
failure_message: str// lib/events.ts
/**
* lib/events.ts
* -------------
* Integration event base type and registry.
* Import event types from here — never define inline in a service file.
*
* Depends on: zod
* Used by: services/*, workers/*
*/
import { z } from 'zod';
import { randomUUID } from 'crypto';
const baseEventSchema = z.object({
event_id: z.string().default(() => randomUUID()),
occurred_at: z.string().datetime().default(() => new Date().toISOString()),
event_type: z.string(),
});
export const orderPlacedV1Schema = baseEventSchema.extend({
event_type: z.literal('order.placed.v1'),
order_id: z.number().int().positive(),
tenant_id: z.string(),
user_id: z.number().int().positive(),
total_cents: z.number().int().positive(),
currency: z.string().length(3),
});
export type OrderPlacedV1 = z.infer<typeof orderPlacedV1Schema>;3. The transactional outbox — never lose an event
The most common event-driven bug: publishing an event after a database commit but before the broker acknowledges. If the process crashes in between, the event is lost.
The outbox pattern fixes this atomically:
1. Write the domain state change AND the event to the DB in one transaction
2. A separate outbox worker reads unpublished events and publishes them
3. Mark events as published only after the broker confirms delivery-- migration: create the outbox table
CREATE TABLE outbox_events (
id BIGSERIAL PRIMARY KEY,
event_id TEXT NOT NULL UNIQUE,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
tenant_id TEXT,
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
published_at TIMESTAMPTZ
);
CREATE INDEX idx_outbox_unpublished ON outbox_events (published, created_at)
WHERE published = FALSE;# services/order_service.py — write state + event in one transaction
async def place_order(user_id: int, items: list) -> Order:
async with db.transaction():
# 1. Write domain state
order = await order_repo.create(user_id=user_id, items=items)
# 2. Write event to outbox in the same transaction
event = OrderPlacedV1(
order_id=order.id,
tenant_id=get_tenant(),
user_id=user_id,
total_cents=order.total_cents,
currency="USD",
)
await db.execute(
"""
INSERT INTO outbox_events (event_id, event_type, payload, tenant_id)
VALUES (:event_id, :event_type, :payload, :tenant_id)
""",
{
"event_id": event.event_id,
"event_type": event.event_type,
"payload": event.model_dump_json(),
"tenant_id": event.tenant_id,
},
)
logger.info("Order placed and event written to outbox. order_id=%s", order.id)
return order# workers/outbox_worker.py
"""
workers/outbox_worker.py
-------------------------
Polls the outbox table and publishes unpublished events to the broker.
Runs as a separate process or background task.
Depends on: lib/broker.py, lib/logger.py
Used by: background worker fleet
"""
import asyncio
from lib.broker import publish
from lib.logger import get_logger
logger = get_logger(__name__)
BATCH_SIZE = 100
POLL_INTERVAL_S = 1.0
async def run_outbox_worker() -> None:
logger.info("Outbox worker started.")
while True:
await _process_batch()
await asyncio.sleep(POLL_INTERVAL_S)
async def _process_batch() -> None:
rows = await db.fetch_all(
"""
SELECT id, event_id, event_type, payload, tenant_id
FROM outbox_events
WHERE published = FALSE
ORDER BY created_at ASC
LIMIT :limit
FOR UPDATE SKIP LOCKED
""",
{"limit": BATCH_SIZE},
)
if not rows:
return
for row in rows:
try:
await publish(
topic=row["event_type"],
key=row["tenant_id"],
value=row["payload"],
)
await db.execute(
"UPDATE outbox_events SET published = TRUE, published_at = now() WHERE id = :id",
{"id": row["id"]},
)
logger.info("Outbox event published. event_id=%s type=%s", row["event_id"], row["event_type"])
except Exception as exc:
logger.error(
"Failed to publish outbox event. event_id=%s type=%s error=%s "
"action=will_retry_on_next_poll",
row["event_id"], row["event_type"], exc,
)4. Idempotent consumers
Consumers must handle duplicate delivery. Use the event_id to deduplicate.
# workers/order_consumer.py
async def handle_event(raw_payload: str) -> None:
import json
data = json.loads(raw_payload)
event_id = data["event_id"]
event_type = data["event_type"]
# Deduplication — already processed?
already = await db.fetch_val(
"SELECT 1 FROM processed_events WHERE event_id = :id", {"id": event_id}
)
if already:
logger.info("Duplicate event skipped. event_id=%s", event_id)
return
# Route to handler
handlers = {
"order.placed.v1": _handle_order_placed,
"payment.failed.v1": _handle_payment_failed,
}
handler = handlers.get(event_type)
if not handler:
logger.warning("Unknown event type, ignoring. event_type=%s", event_type)
return
await handler(data)
# Mark processed atomically
await db.execute(
"INSERT INTO processed_events (event_id, event_type, processed_at) VALUES (:id, :type, now())",
{"id": event_id, "type": event_type},
)5. Event versioning — backward-compatible changes only
Once an event schema is in production, consumers depend on it.
| Change | Safe? | How |
| Add optional field | ✅ | Add with default; old consumers ignore it |
| Remove field | ❌ | Bump version: order.placed.v2; run both in parallel |
| Rename field | ❌ | Bump version |
| Change field type | ❌ | Bump version |
| Add required field | ❌ | Bump version |
Running two versions in parallel:
# Producer publishes both during migration window
await publish_event(OrderPlacedV1(...)) # old consumers
await publish_event(OrderPlacedV2(...)) # new consumers
# Once all consumers migrate to v2, stop publishing v16. Ordering and out-of-order delivery
Most brokers guarantee ordering within a partition/key — not globally.
# Use a stable partition key so related events are ordered
await publish(
topic="order.placed.v1",
key=str(order.id), # all events for the same order go to the same partition
value=event.model_dump_json(),
)Design consumers to handle out-of-order delivery:
async def _handle_order_placed(data: dict) -> None:
order_id = data["order_id"]
# Idempotent check — if order already exists and is further along, skip
existing = await order_repo.find_by_id(order_id)
if existing and existing["status"] != "pending":
logger.info(
"Order already past 'placed' state, skipping event. "
"order_id=%s current_status=%s",
order_id, existing["status"],
)
return
# ... processRules
- Define the event schema before writing the producer or consumer. The schema is the contract.
- Use the transactional outbox — never publish after a DB commit without it. Dual writes lose events.
- Version event types in the type string (
order.placed.v1). Never mutate a published schema. - Every consumer must deduplicate on
event_id. At-least-once delivery is the default for every broker. - Partition on a stable business key (order ID, user ID) to preserve ordering for related events.
- Log
event_idandevent_typeon every log line in a consumer. They are the only way to correlate with the producer. - Consume unknown event types silently (log + skip) — never crash. New event types will appear as producers evolve.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

