Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tools-only/X-Skills --skill implement-events명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
SOC 직업 분류 기준
| name | implement-events |
| description | Implement event-driven API architecture |
| shortcut | even |
Build production-grade event-driven APIs with message queues, event streaming, and async communication patterns. This command generates event publishers, subscribers, message brokers integration, and event-driven architectures for microservices and distributed systems.
Why event-driven architecture:
Alternatives considered:
This approach balances: Loose coupling, reliability, scalability, and operational complexity.
Use event-driven architecture when:
Don't use when:
Choose Message Broker
Define Event Schemas
Implement Publishers
Build Subscribers
Add Event Patterns
// events/EventPublisher.js
const amqp = require('amqplib');
const { v4: uuidv4 } = require('uuid');
class EventPublisher {
constructor(connectionUrl) {
this.connectionUrl = connectionUrl;
this.connection = null;
this.channel = null;
}
async connect() {
this.connection = await amqp.connect(this.connectionUrl);
this.channel = await this.connection.createChannel();
// Declare exchange for fanout (pub/sub)
await this.channel.assertExchange('events', 'topic', { durable: true });
console.log('Event publisher connected to RabbitMQ');
}
async publish(eventName, payload) {
(!.) {
();
}
event = {
: (),
: eventName,
: ().(),
: ,
: payload
};
routingKey = eventName;
message = .(.(event));
published = ..(
,
routingKey,
message,
{
: ,
: ,
: event.,
: .()
}
);
(!published) {
();
}
.(, event.);
event.;
}
() {
.?.();
.?.();
}
}
. = ;
publisher = ();
publisher.();
router.(, (req, res) => {
{
user = (req.);
publisher.(, {
: user.,
: user.,
: user.
});
res.().(user);
} (error) {
(error);
}
});
// events/EventSubscriber.js
const amqp = require('amqplib');
class EventSubscriber {
constructor(connectionUrl, queueName) {
this.connectionUrl = connectionUrl;
this.queueName = queueName;
this.handlers = new Map();
}
async connect() {
this.connection = await amqp.connect(this.connectionUrl);
this.channel = await this.connection.createChannel();
// Declare exchange
await this.channel.assertExchange('events', 'topic', { durable: true });
// Declare queue with dead-letter exchange
await this.channel.assertQueue(this.queueName, {
durable: true,
deadLetterExchange: ,
:
});
.();
}
() {
..(eventName, handler);
..(., , eventName);
.();
}
() {
..();
..(., (message) => {
(!message) ;
content = message..();
event = .(content);
.(, event.);
handler = ..(event.);
(!handler) {
.();
..(message);
;
}
{
alreadyProcessed = (event.);
(alreadyProcessed) {
.();
..(message);
;
}
(event., event);
(event.);
..(message);
} (error) {
.(, error);
..(message, , );
}
});
.();
}
}
subscriber = (, );
subscriber.();
subscriber.(, (data, event) => {
(data., data.);
.();
});
subscriber.(, (data, event) => {
(data., data.);
.();
});
subscriber.();
# events/kafka_producer.py
from kafka import KafkaProducer
from kafka.errors import KafkaError
import json
import uuid
from datetime import datetime
from typing import Dict, Any
class EventProducer:
def __init__(self, bootstrap_servers: str):
self.producer = KafkaProducer(
bootstrap_servers=bootstrap_servers,
value_serializer=lambda v: json.dumps(v).encode('utf-8'),
acks='all', # Wait for all replicas
retries=3,
max_in_flight_requests_per_connection=1 # Preserve order
)
def publish(self, topic: str, event_name: str, payload: Dict[str, Any]) -> str:
event_id = str(uuid.uuid4())
event = {
'id': event_id,
'name': event_name,
'timestamp': datetime.utcnow().isoformat(),
'version': '1.0.0',
'data': payload
}
future = self.producer.send(
topic,
value=event,
key=event_id.encode('utf-8')
)
:
record_metadata = future.get(timeout=)
(
)
event_id
KafkaError e:
()
():
.producer.flush()
.producer.close()
producer = EventProducer()
():
user = create_user(request.data)
producer.publish(
topic=,
event_name=,
payload={
: user.,
: user.email,
: user.name
}
)
{: user.to_dict()},
// Event sourcing: Store events as source of truth
class OrderEventStore {
constructor(publisher) {
this.publisher = publisher;
}
async placeOrder(orderId, items, customerId) {
// Publish event (this is the source of truth)
await this.publisher.publish('order.placed', {
orderId,
items,
customerId,
status: 'pending',
timestamp: new Date().toISOString()
});
}
async confirmPayment(orderId, paymentId) {
await this.publisher.publish('order.payment_confirmed', {
orderId,
paymentId,
timestamp: new Date().toISOString()
});
}
async shipOrder(orderId, trackingNumber) {
await this.publisher.publish('order.shipped', {
orderId,
trackingNumber,
timestamp: new Date().toISOString()
});
}
}
// Rebuild order state from events
() {
events = ();
order = { : orderId };
( event events) {
(event.) {
:
order = { ...order, ...event., : };
;
:
order. = ;
order. = event..;
;
:
order. = ;
order. = event..;
;
}
}
order;
}
// Write model: Handle commands, emit events
class OrderCommandHandler {
async handlePlaceOrder(command) {
// Validate
if (!command.items.length) {
throw new Error('Order must have items');
}
// Create order (write)
const order = await db.orders.create({
customerId: command.customerId,
items: command.items,
status: 'pending'
});
// Emit event
await publisher.publish('order.placed', {
orderId: order.id,
customerId: order.customerId,
totalAmount: calculateTotal(order.items)
});
return order.id;
}
}
// Read model: Listen to events, update read-optimized views
subscriber.on('order.placed', async (data) => {
// Update denormalized view for fast queries
await redis.set(`order:${data.orderId}`, JSON.({
: data.,
: data.,
: data.,
: ,
: ().()
}));
redis.(, , );
});
// Orchestrate multi-service transaction with compensating actions
class OrderSaga {
async execute(orderData) {
const sagaId = uuidv4();
try {
// Step 1: Reserve inventory
await publisher.publish('inventory.reserve', {
sagaId,
items: orderData.items
});
await waitForEvent('inventory.reserved', sagaId);
// Step 2: Process payment
await publisher.publish('payment.process', {
sagaId,
amount: orderData.amount,
customerId: orderData.customerId
});
await waitForEvent('payment.processed', sagaId);
// Step 3: Create shipment
await publisher.publish('shipment.create', {
sagaId,
orderId: orderData.orderId,
address: orderData.shippingAddress
});
await waitForEvent('shipment.created', sagaId);
// Saga completed successfully
await publisher.publish('order.saga_completed', { sagaId });
} catch (error) {
.(, error);
publisher.(, { sagaId });
publisher.(, { sagaId });
publisher.(, { sagaId });
publisher.(, { sagaId, : error. });
}
}
}
Common issues and solutions:
Problem: Events lost during broker outage
Problem: Duplicate event processing
Problem: Events processed out of order
Problem: Subscriber can't keep up with events
Problem: Dead-letter queue fills up
Transactional outbox pattern (prevent lost events):
// Atomic database write + event publish
async function createUserWithEvent(userData) {
const transaction = await db.transaction();
try {
// 1. Create user in database
const user = await db.users.create(userData, { transaction });
// 2. Store event in outbox table (same transaction)
await db.outbox.create({
eventName: 'user.created',
payload: { userId: user.id, email: user.email },
published: false
}, { transaction });
await transaction.commit();
// 3. Background job publishes from outbox
// If app crashes, outbox worker retries unpublished events
} catch (error) {
await transaction.rollback();
throw error;
}
}
const userCreatedSchema = {
$schema: "http://json-schema.org/draft-07/schema#",
type: "object",
required: ["id", "name", "timestamp", "version", "data"],
properties: {
id: { type: "string", format: "uuid" },
name: { type: "string", const: "user.created" },
timestamp: { type: "string", format: "date-time" },
version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+$" },
data: {
type: "object",
required: ["userId", "email"],
properties: {
userId: { type: "integer" },
email: { type: "string", format: "email" },
name: { type: "string", minLength: 1 }
}
}
}
};
const rabbitConfig = {
url: process.env.RABBITMQ_URL || 'amqp://localhost',
exchange: {
name: 'events',
type: 'topic', // Supports wildcard routing (user.*, order.created)
durable: true // Survive broker restart
},
queue: {
durable: true,
deadLetterExchange: 'events.dlx',
messageTtl: 86400000, // 24 hours
maxLength: 100000, // Max messages in queue
maxPriority: 10 // Priority queue support
},
publisher: {
confirm: true, // Wait for broker acknowledgment
persistent: true // Messages survive broker restart
},
subscriber: {
prefetch: 1, // Messages to prefetch
noAck: false, // Manual acknowledgment
exclusive: false // Allow multiple consumers
}
};
DO:
DON'T:
TIPS:
<entity>.<action> (user.created, order.shipped)/build-api-gateway - Route events through API gateway/generate-rest-api - Generate REST API that publishes events/create-monitoring - Monitor event processing metrics/implement-throttling - Rate limit event publishing/scan-api-security - Security scan event handlersOptimization strategies:
// Batch events for higher throughput
const eventBatch = [];
setInterval(async () => {
if (eventBatch.length > 0) {
await publisher.publishBatch(eventBatch);
eventBatch.length = 0;
}
}, 100); // Flush every 100ms
// Parallel event processing (if order doesn't matter)
subscriber.channel.prefetch(10); // Process 10 messages concurrently
Security checklist:
// Use TLS for RabbitMQ
const connection = await amqp.connect('amqps://user:pass@broker:5671', {
ca: [fs.readFileSync('ca-cert.pem')],
cert: fs.readFileSync('client-cert.pem'),
key: fs.readFileSync('client-key.pem')
});
// Validate event schemas
const Ajv = require('ajv');
const ajv = new Ajv();
const validate = ajv.compile(eventSchema);
function publishEvent(event) {
if (!validate(event)) {
throw new Error(`Invalid event: ${ajv.errorsText(validate.errors)}`);
}
// Proceed with publish
}
Events not being consumed:
High event processing lag:
Events being processed multiple times:
Dead-letter queue filling up: