Event Catalog & Domain Taxonomy
tektree intermediate 5 min read
ELI5
The catalog is the post office’s address book: every parcel type has a label like domain.thing.action so the right department knows to subscribe. Knowledge ships questions, gamification awards XP, payment radios in subscription changes — all using the same address grammar.
Technical Deep Dive
docs/docs/architecture/EVENT_CATALOG.md is the single source of truth for what events exist, who publishes them, and who consumes them. The taxonomy is {domain}.{aggregate}.{action} — three dot-separated lowercase segments — chosen so subscribers can wildcard a domain (Subscribe("knowledge.*", h)) or narrow to an aggregate ("knowledge.question.*").
Domain Map
flowchart LR subgraph user U1[user.registered] U2[user.profile.updated] U3[user.authenticated] end subgraph knowledge K1[knowledge.question.posted] K2[knowledge.answer.submitted] K3[knowledge.answer.accepted] K4[knowledge.insight.published] K5[knowledge.discussion.started] K6[knowledge.resource.shared] end subgraph social S1[social.content.liked] S2[social.comment.added] S3[social.content.upvoted] S4[social.user.followed] end subgraph gamification G1[gamification.xp.earned] G2[gamification.level.up] G3[gamification.achievement.unlocked] G4[gamification.streak.extended] G5[gamification.leaderboard.updated] end subgraph payment P1[payment.subscription.created] P2[payment.subscription.upgraded] P3[payment.quota.exceeded] P4[payment.webhook.received] end subgraph realtime R1[realtime.user.joined] R2[notification.sent] end K1 --> G1 K2 --> G1 K3 --> G1 K4 --> G1 S3 --> G1 G1 --> G2 P1 --> R2 G3 --> R2 K1 --> R2Producers and Consumers (sample)
| Event | Producer | Consumers |
|---|---|---|
user.registered | user-service | gamification (welcome XP), notification, search-index |
knowledge.question.posted | knowledge-service | gamification (+5 XP), search-index, notification (followers) |
knowledge.answer.accepted | knowledge-service | gamification (+25 to answerer) |
payment.subscription.created | payment-service | user-service (tier flip), notification |
gamification.level.up | gamification-service | notification, realtime |
notification.sent | notification-service | realtime-service (fan-out) |
Versioning
Every event carries a Version string in its envelope (semver). Backwards-compatible additions bump minor ("1.0" → "1.1"); breaking payload changes either bump major or introduce a new type with a different aggregate name (knowledge.question.posted.v2). Consumers should switch on (Type, Version) and tolerate unknown future minors.
Persisted Audit
DATA_MODELS.md lists an events collection in MongoDB that mirrors the bus output: event_id, event_type, event_version, timestamp, trace_id, correlation_id, source_service, producer_id, metadata, payload, indexed by event_type + timestamp and payload.user_id + timestamp. The bus is the live channel; the collection is the audit trail / replay source.
”social” vs “knowledge”
Likes and upvotes are social.* even when they target knowledge entities — the verb is what determines the domain, not the target. This keeps consumer responsibilities clean: gamification listens for social.* upvote events without subscribing to the entire knowledge.* firehose.
Key Terms
- Domain → top segment of the type, owned by one service.
- Aggregate → middle segment, the noun an action happens to.
- Action → bottom segment, past-tense verb (
posted,created,unlocked). - Wildcard pattern → subscribe with
"<domain>.*"or"<domain>.<aggregate>.*". eventscollection → durable audit; not the read-time source of truth.
Q&A
Q: A new “team invitation accepted” event is needed. Should it be team.invitation.accepted or user.invitation.accepted?
A: Whichever service owns the team aggregate. If teams are a user-service concern, user.invitation.accepted. If a team-service is added (a new domain), team.invitation.accepted. The owner is the publisher.
Q: Why isn’t notification.sent namespaced under a notification.* domain?
A: It is — the EVENT_CATALOG groups it under realtime/notification context, but the type itself is notification.sent. The flowchart above places it under realtime because that is the dominant consumer; the producer is the notification-service.
Q: Can two services publish the same event type?
A: They can, but the catalog discourages it: each type has one declared producer. Multiple producers make replay and source-attribution ambiguous, even though the Source field disambiguates at runtime.
Examples
Adding a “question viewed” event for analytics: choose knowledge.question.viewed (knowledge owns the aggregate), version "1.0", payload {question_id, viewer_id, viewed_at}. Document publisher (knowledge-service), consumers (analytics, gamification daily-streak), and add a row to EVENT_CATALOG.md before deploying the publisher.
neighbors on the map
- NATS Subject Taxonomy wiring a new consumer to the right stream
- CI Transition Event Schema vendoring kahn_emit.py into a CI producer