CRUMB a card from devarno-cloud

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 --> R2

Producers and Consumers (sample)

EventProducerConsumers
user.registereduser-servicegamification (welcome XP), notification, search-index
knowledge.question.postedknowledge-servicegamification (+5 XP), search-index, notification (followers)
knowledge.answer.acceptedknowledge-servicegamification (+25 to answerer)
payment.subscription.createdpayment-serviceuser-service (tier flip), notification
gamification.level.upgamification-servicenotification, realtime
notification.sentnotification-servicerealtime-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>.*".
  • events collection → 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