创建事件处理器
diegosouzapw2026-10-27查看 1448复制 1882
按照 ModuleImplementationGuide.md 第 9 节设置 RabbitMQ 事件发布者和消费者。仅限 RabbitMQ(不使用 Azure Service Bus)。创建带有 DomainEvent 的发布者(优先使用 tenantId),创建带有处理程序的消费者,命名格式为 {domain}.{entity}.{action},必填字段包括(id、type、version、timestamp、tenantId、source、data)。在添加事件驱动通信、异步工作流或通过事件进行集成时使用。
📘如何使用这个技能
- 1复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
- 2打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
- 3粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
- 4直接提出你的需求·AI 会按技能定义的工作流、格式和约束来处理,无需再解释背景
💡 提示:技能生效后,每次对话只需一句需求,AI 会按 SKILL.md 的流程执行。
SKILL.md8050 chars
---
name: create-event-handlers
description: Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9. RabbitMQ only (no Azure Service Bus). Creates publishers with DomainEvent (tenantId preferred), consumers with handlers, naming {domain}.{entity}.{action}, required fields (id, type, version, timestamp, tenantId, source, data). Use when adding event-driven communication, async workflows, or integrating via events.
---
# Create Event Handlers
Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9.
## Event Naming Convention
Reference: ModuleImplementationGuide.md Section 9.1
Format: `{domain}.{entity}.{action}`
✅ **Correct:**
- `user.created`
- `auth.login.success`
- `notification.email.sent`
- `secret.rotated`
❌ **Wrong:**
- `userCreated`
- `loginSuccess`
- `emailSent`
**Standard Actions:**
- `created`, `updated`, `deleted`
- `started`, `completed`, `failed`
- `sent`, `received`, `expired`, `rotated`
## Event Structure
Reference: ModuleImplementationGuide.md Section 9.2
```typescript
interface DomainEvent<T = unknown> {
// Identity
id: string; // Unique event ID (UUID)
type: string; // Event type (domain.entity.action)
// Metadata
timestamp: string; // ISO 8601
version: string; // Event schema version
source: string; // Module that emitted
correlationId?: string; // Request correlation
// Context
tenantId?: string; // Tenant context (PREFERRED; use for new modules)
organizationId?: string; // DEPRECATED for new modules; prefer tenantId
userId?: string; // Actor
// Payload
data: T; // Event-specific data
}
```
## Event Publisher
Reference: containers/auth/src/events/publishers/AuthEventPublisher.ts
### src/events/publishers/[Module]EventPublisher.ts
```typescript
import { randomUUID } from 'crypto';
import { EventPublisher, getChannel, closeConnection } from '@coder/shared';
import { log } from '../../utils/logger';
import { getConfig } from '../../config';
let publisher: EventPublisher | null = null;
export async function initializeEventPublisher(): Promise<void> {
if (publisher) return;
const config = getConfig();
if (!config.rabbitmq?.url) {
log.warn('RabbitMQ URL not configured, events will not be published');
return;
}
try {
await getChannel();
publisher = new EventPublisher(config.rabbitmq.exchange || 'coder_events');
log.info('Event publisher initialized', { exchange: config.rabbitmq.exchange });
} catch (error: any) {
log.error('Failed to initialize event publisher', error);
}
}
export async function closeEventPublisher(): Promise<void> {
try {
await closeConnection();
publisher = null;
} catch (error: any) {
log.error('Error closing event publisher', error);
}
}
function getPublisher(): EventPublisher | null {
if (!publisher) {
const config = getConfig();
publisher = new EventPublisher(config.rabbitmq.exchange || 'coder_events');
}
return publisher;
}
export function createBaseEvent(
type: string,
userId?: string,
tenantId?: string,
correlationId?: string,
data?: any
) {
return {
id: randomUUID(),
type,
timestamp: new Date().toISOString(),
version: '1.0',
source: '[module-name]',
correlationId,
tenantId,
userId,
data: data || {},
};
}
export async function publishEvent(event: any, routingKey?: string): Promise<void> {
const pub = getPublisher();
if (!pub) {
log.warn('Event publisher not initialized, skipping event', { type: event.type });
return;
}
try {
await pub.publish(routingKey || event.type, event);
log.debug('Event published', { type: event.type, id: event.id });
} catch (error: any) {
log.error('Failed to publish event', error, { type: event.type });
}
}
```
### Usage in Services
```typescript
import { publishEvent, createBaseEvent } from '../events/publishers/ModuleEventPublisher';
// Publish event (tenantId for tenant context)
const event = createBaseEvent(
'resource.created',
userId,
tenantId,
correlationId,
{
resourceId: resource.id,
name: resource.name,
}
);
await publishEvent(event);
```
## Event Consumer
Reference: ModuleImplementationGuide.md Section 9.4
### src/events/consumers/[Resource]Consumer.ts
```typescript
import { EventConsumer } from '@coder/shared';
import { log } from '../../utils/logger';
import { getConfig } from '../../config';
let consumer: EventConsumer | null = null;
export async function initializeEventConsumer(): Promise<void> {
if (consumer) return;
const config = getConfig();
if (!config.rabbitmq?.url) {
log.warn('RabbitMQ URL not configured, events will not be consumed');
return;
}
try {
consumer = new EventConsumer({
queue: config.rabbitmq.queue || '[module-name]_service',
exchange: config.rabbitmq.exchange || 'coder_events',
bindings: config.rabbitmq.bindings || [],
});
// Register handlers
consumer.on('other.resource.created', handleResourceCreated);
consumer.on('other.resource.updated', handleResourceUpdated);
await consumer.start();
log.info('Event consumer initialized', { queue: config.rabbitmq.queue });
} catch (error: any) {
log.error('Failed to initialize event consumer', error);
}
}
async function handleResourceCreated(event: any): Promise<void> {
log.info('Resource created event received', { resourceId: event.data.resourceId });
// Handle the event
}
async function handleResourceUpdated(event: any): Promise<void> {
log.info('Resource updated event received', { resourceId: event.data.resourceId });
// Handle the event
}
export async function closeEventConsumer(): Promise<void> {
try {
if (consumer) {
await consumer.stop();
consumer = null;
}
} catch (error: any) {
log.error('Error closing event consumer', error);
}
}
```
## Event Documentation
Reference: ModuleImplementationGuide.md Section 9.5
### logs-events.md (if events are logged)
Create in module root if module publishes events that get logged:
```markdown
# [Module Name] - Logs Events
## Published Events
### {domain}.{entity}.{action}
**Description**: When this event is triggered.
**Triggered When**:
- Condition 1
- Condition 2
**Event Type**: `{domain}.{entity}.{action}`
**Event Schema**:
\`\`\`json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "type", "timestamp", "version", "source", "data"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"type": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" },
"version": { "type": "string" },
"source": { "type": "string" },
"tenantId": { "type": "string", "format": "uuid" },
"userId": { "type": "string", "format": "uuid" },
"data": {
"type": "object",
"properties": {
"resourceId": { "type": "string" }
}
}
}
}
\`\`\`
```
### notifications-events.md (if events trigger notifications)
Create in module root if module publishes events that trigger notifications.
## Configuration
Add to config/default.yaml:
```yaml
rabbitmq:
url: ${RABBITMQ_URL}
exchange: coder_events
queue: [module-name]_service
bindings:
- "other.resource.created"
- "other.resource.updated"
```
## Checklist
- [ ] Event publisher created following pattern
- [ ] Event consumer created (if consuming events)
- [ ] Events follow naming convention: {domain}.{entity}.{action}
- [ ] Events include all required fields (id, type, version, timestamp, source, data)
- [ ] Events include tenantId when applicable (RabbitMQ only; no Azure Service Bus)
- [ ] logs-events.md created (if events are logged)
- [ ] notifications-events.md created (if events trigger notifications)
- [ ] RabbitMQ config added to default.yaml