# Gateway Event Design โ€” The Pulse of Discord > *"Events are the heartbeat of a distributed system."* > โ€” Martin Fowler --- ## ๐ŸŽฏ Filosofi Gateway Events Discord Gateway adalah **jantung event-driven** BETE: 1. **Single source of truth** โ€” Events adalah satu-satunya cara data bergerak antar service 2. **At-least-once delivery** โ€” Event bisa terkirim lebih dari sekali (idempotent consumers) 3. **Schema evolution** โ€” Events punya versioning untuk backward compatibility 4. **Observable** โ€” Setiap event tercatat untuk debugging dan audit --- ## ๐Ÿ“ฆ Event Schema ### Envelope ```typescript interface GatewayEvent { /** Event type identifier โ€” lowercase, colon-separated */ type: string; /** Event payload */ data: T; /** ISO 8601 timestamp of when the event was created */ timestamp: string; /** Unique event ID for deduplication */ eventId: string; /** Source service name */ source: 'discord-gateway'; /** Event schema version */ version: number; /** Optional correlation ID for tracing request flows */ correlationId?: string; } ``` ### Event Size Limits | Limit | Value | Notes | |-------|-------|-------| | Max payload size | 256KB | Larger payloads โ†’ reference via URL | | Max nesting depth | 5 levels | Prevent billion laughs attack | | String max length | 100KB | Truncate with `... (truncated)` suffix | --- ## ๐Ÿ“‹ Event Catalog ### Message Events ```typescript // discord:message:created interface MessageCreatedEvent { id: string; channelId: string; guildId: string; author: { id: string; name: string; discriminator: string; avatar: string | null; isBot: boolean; }; content: string; timestamp: string; // ISO 8601 editedTimestamp: string | null; attachments: AttachmentInfo[]; replyTo?: string; // Parent message ID } // discord:message:updated interface MessageUpdatedEvent { id: string; channelId: string; content: string; editedTimestamp: string; } // discord:message:deleted interface MessageDeletedEvent { id: string; channelId: string; guildId: string; } ``` ### Analysis Events ```typescript // discord:message:analyzed interface MessageAnalyzedEvent { messageId: string; status: 'pending' | 'complete' | 'error'; severity: 'safe' | 'low' | 'medium' | 'high' | 'critical'; categories: string[]; confidence: number; // 0โ€“1 summary: string; analyzedAt: string; processingTimeMs: number; } ``` ### Voice Events ```typescript // discord:voice:started interface VoiceStartedEvent { guildId: string; channelId: string; channelName: string; startedAt: string; participants: Array<{ userId: string; userName: string; }>; } // discord:voice:stopped interface VoiceStoppedEvent { guildId: string; channelId: string; duration: number; // seconds segmentsCount: number; } // discord:voice:uploaded interface VoiceUploadedEvent { segmentId: string; guildId: string; channelId: string; userId: string; userName: string; duration: number; fileUrl: string; fileSize: number; timestamp: string; } ``` ### Attachment Events ```typescript // discord:attachment:created interface AttachmentCreatedEvent { id: string; messageId: string; channelId: string; url: string; filename: string; contentType: string; size: number; width?: number; height?: number; } // discord:attachment:uploaded interface AttachmentUploadedEvent { id: string; messageId: string; storageUrl: string; thumbnailUrl?: string; fileSize: number; processingTimeMs: number; } ``` --- ## ๐Ÿ”„ Event Lifecycle ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Discord โ”‚ (messageCreate, voiceStateUpdate, etc.) โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ†“ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Discord.js โ”‚ (client events) โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ†“ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Message Capture Controller โ”‚ (messageCapture.ts) โ”‚ - Parse event โ”‚ โ”‚ - Store in database โ”‚ โ”‚ - Publish to Redis โ”‚ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ†“ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Redis Pub/Sub โ”‚ (channel: discord:message:created) โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ†“ โ†“ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Backend Service โ”‚ โ”‚ AI Moderation โ”‚ โ”‚ - Index message โ”‚ โ”‚ - Analyze text โ”‚ โ”‚ - Store in DB โ”‚ โ”‚ - Update status โ”‚ โ”‚ - Broadcast WS โ”‚ โ”‚ - Publish result โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ## ๐Ÿงช Event Testing ```typescript // Helper untuk generate test events function createTestEvent(type: string, data: T): GatewayEvent { return { type, data, timestamp: new Date().toISOString(), eventId: crypto.randomUUID(), source: 'discord-gateway', version: 1, }; } describe('MessageCreatedEvent', () => { it('is properly formatted', () => { const event = createTestEvent('discord:message:created', { id: 'msg_1', channelId: 'ch_1', guildId: 'guild_1', author: { id: 'user_1', name: 'Test', discriminator: '0000', avatar: null, isBot: false }, content: 'Hello world', timestamp: new Date().toISOString(), editedTimestamp: null, attachments: [], }); expect(event.type).toBe('discord:message:created'); expect(event.data.content).toBe('Hello world'); expect(event.source).toBe('discord-gateway'); expect(event.version).toBe(1); }); }); ``` --- ## ๐Ÿ“Š Event Performance Metrics | Metric | Target | Alert | |--------|--------|-------| | Processing latency | <50ms p99 | >200ms | | Event throughput | >1000/s | <100/s (unusual) | | Redis publish latency | <5ms | >20ms | | Event loss rate | 0% | >0.01% | | Queue depth | <100 | >1000 | --- ## โš ๏ธ Anti-Patterns Events ### โŒ Processing-heavy event handlers ```typescript // โŒ JANGAN โ€” blocking event loop eventBus.on('message:created', async (event) => { const result = await expensiveAnalysis(event.data.content); await db.save(result); // Event handler for 100 msg/s = bottleneck }); // โœ… Queue heavy work eventBus.on('message:created', async (event) => { await analysisQueue.add(event); // Worker processes async }); ``` ### โŒ Missing idempotency ```typescript // โŒ JANGAN โ€” duplicate events create duplicate records async function handleMessageCreated(event) { await db.insert({ id: event.data.id, content: event.data.content }); // If event arrives twice โ†’ duplicate key error } // โœ… Idempotent: UPSERT async function handleMessageCreated(event) { await db.upsert({ id: event.data.id }, { content: event.data.content }); } ``` --- ## ๐Ÿ”— Referensi | Sumber | Konsep | |--------|--------| | [Redis Pub/Sub](https://redis.io/docs/manual/pubsub/) | Event backbone | | [CloudEvents](https://cloudevents.io/) | Event schema standard | | [Discord Gateway](https://discord.com/developers/docs/topics/gateway) | Discord events | --- *"Setiap event adalah denyut nadi โ€” tanda bahwa sistem masih hidup dan berbicara."* โ„๏ธ๐Ÿฉต