96 lines
5.4 KiB
Go
96 lines
5.4 KiB
Go
// Package event is the platform's shared event vocabulary: one envelope shape
|
||
// (Event) and a small set of Type constants for the event meanings that
|
||
// genuinely cross module boundaries. It imports only the stdlib — never an app
|
||
// module, never a transport library — so every module can depend on it.
|
||
//
|
||
// Discipline: a Type constant earns its place only when a real producer emits it
|
||
// AND it is meaningful to another module. We do not pre-declare aspirational
|
||
// types. See docs/platform-event-messaging-sketch.md.
|
||
package event
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"time"
|
||
)
|
||
|
||
// Type is the semantic event kind — the cross-module contract. A module may use
|
||
// its own private kinds internally; only meanings that cross a boundary belong
|
||
// here.
|
||
type Type string
|
||
|
||
const (
|
||
// Editorial (graph-cms). Emitted by the CMS outbound bridge on content-graph
|
||
// mutations. In-repo consumers are TBD (external generators today); this is
|
||
// the first envelope producer, so the constants exist where they are emitted.
|
||
ContentCreated Type = "content.created"
|
||
ContentUpdated Type = "content.updated"
|
||
ContentDeleted Type = "content.deleted"
|
||
|
||
// Catalog (finder). These meanings already cross modules today — finder
|
||
// writer → reader and → backoffice (MountFinderListener) — but still travel
|
||
// as finder's legacy ChangeTopic strings on the wire. The constants document
|
||
// the shared vocabulary; the wire migration onto the envelope is a later step.
|
||
CatalogItemChanged Type = "catalog.item_changed" // legacy: finder "item_added"
|
||
CatalogSnapshotReady Type = "catalog.snapshot_ready" // legacy: finder "snapshot_ready"
|
||
CatalogSettingsChanged Type = "catalog.settings_changed" // legacy: finder "settings_change"
|
||
CatalogFacetChanged Type = "catalog.facet_changed" // legacy: finder "facet_change"
|
||
CatalogAccessoryChanged Type = "catalog.accessory_changed" // legacy: finder "accessory_change"
|
||
// CatalogProjectionPublished carries the commerce-facing catalog Projection
|
||
// (platform/catalog) — producer maps raw items → Projection once, consumers
|
||
// (cart / checkout / order / UCP) read the typed shape. Distinct from
|
||
// CatalogItemChanged (raw bodies, consumed by inventory/backoffice): a
|
||
// product index emits BOTH; a non-product index emits neither projection.
|
||
// Payload is a []catalog.ProjectionUpdate batch (upserts + deletes per SKU).
|
||
CatalogProjectionPublished Type = "catalog.projection_published"
|
||
CatalogSortOverride Type = "catalog.sort_override" // finder-internal: per-session/group/popularity overrides
|
||
CatalogFieldSortOverride Type = "catalog.field_sort_override" // finder-internal: live field ordering overrides
|
||
|
||
// Inventory (go-redis-inventory core, emitted by the cart-inventory service).
|
||
// A coarse stock-level crossing (none/low/high) for one SKU+location —
|
||
// never a quantity. Exact stock is a synchronous query against the inventory
|
||
// service; only level crossings cross a module boundary. Payload is an
|
||
// inventory.LevelChanged (platform/inventory). First consumer: finder stock
|
||
// overlay (display hints). See docs/inventory-shape.md.
|
||
InventoryLevelChanged Type = "inventory.level_changed"
|
||
|
||
// Commerce (order). Emitted by the order service saga after an order is
|
||
// placed-and-paid (a durable outbox event). The authoritative "a sale
|
||
// happened" fact — multiple modules react independently (inventory commits
|
||
// stock, fulfillment allocates) without checkout coupling to any of them.
|
||
// Payload is an order.Created (platform/order). See docs/inventory-shape.md
|
||
// and docs/fulfillment-stance.md.
|
||
OrderCreated Type = "order.created"
|
||
|
||
// Analytics (tracking). Replaces the integer enum 0–6 that finder and
|
||
// slask-tracking use on the `tracking` topic today. Producer: slask-finder
|
||
// (pkg/tracking/rabbit-tracking.go). Consumer: slask-tracking
|
||
// (pkg/events/rabbit-tracking.go).
|
||
TrackSession Type = "track.session" // 0 — session start
|
||
TrackSearch Type = "track.search" // 1 — search query
|
||
TrackClick Type = "track.click" // 2 — item click / product view
|
||
TrackCart Type = "track.cart" // 3,4 — add to / remove from cart
|
||
TrackImpression Type = "track.impression" // 5 — impression
|
||
TrackAction Type = "track.action" // 6 — custom action (wishlist, etc.)
|
||
TrackPageView Type = "track.page_view"
|
||
TrackPageEngaged Type = "track.page_engaged"
|
||
TrackPageAction Type = "track.page_action"
|
||
)
|
||
|
||
// Event is the one envelope. Payload is per-Type, agreed between producer and
|
||
// consumer (json.RawMessage, not a god-struct). A nil Payload means "the subject
|
||
// changed; fetch the latest state."
|
||
type Event struct {
|
||
ID string `json:"id"` // unique; enables idempotent/at-least-once dedupe
|
||
Type Type `json:"type"` // the vocabulary
|
||
Source string `json:"source"` // module identifier ("graph-cms", "finder", ...)
|
||
Subject string `json:"subject"` // the resource that changed (node id, sku, ...)
|
||
Payload json.RawMessage `json:"payload,omitempty"` // per-Type body, or nil
|
||
Time time.Time `json:"time"`
|
||
Meta map[string]string `json:"meta,omitempty"` // trace id, causation, country, dataType, ...
|
||
}
|
||
|
||
// Handler consumes an event. Returning an error means "not handled" — durable
|
||
// transports may redeliver; fire-and-forget transports just log.
|
||
type Handler func(ctx context.Context, e Event) error
|