Skip to content
Spice Framework on GitHub

Typed application events

frameworkMaturity: previewSource: spice@c0641b3Exact reviewed source

Spice event topics are explicit generic values, not a process-global registry:

topic, err := event.NewTopic(
event.Definition{
ID: "orders.OrderPlaced",
Module: "example.com/shop/orders",
},
[]event.Subscriber[OrderPlaced]{
{
ID: "inventory.Reserve",
Module: "example.com/shop/inventory",
Order: 10,
Handle: inventory.Reserve,
},
},
interactionObserver,
)

The compiler can now derive this topic contract from valid Go declarations:

// @event.Topic
type OrderPlaced struct {
OrderID string
}
// @event.Listener(order=10)
func (*Inventory) Reserve(context.Context, OrderPlaced) error {
return nil
}

The payload declaration contributes a synthetic exact event.Publisher[OrderPlaced] provider. Listener receivers become ordinary exact dependencies discovered from the typed listener model. The compiler validates method signatures, provider ownership, exported payload identity, unique topic selection, module ownership, deterministic order, and the resulting provider graph. Generation calls event.NewTopic directly, binds each listener method to its already-constructed provider receiver, and assigns the topic to the synthetic exact publisher variable. No annotation target or provider body executes during analysis.

Producers depend on event.Publisher[OrderPlaced], preserving the exact event payload type at compile time. ApplicationOptions.EventObservers supplies instance-owned observers to every generated topic. Invalid observer configuration fails construction and rolls back earlier providers in reverse order. Subscriber order is stable: lower explicit order first, then module import path and stable subscriber ID. The constructor copies its inputs, starts no goroutine, scans no package, and installs no global state.

Publish is synchronous and fail-fast. It uses the caller’s context and goroutine, stops before the next subscriber when cancellation or an error is observed, and wraps failures with event/subscriber identity. A handler panic is reported to observers and re-raised with its original value. Applications therefore retain ordinary Go control flow and can decide where an event belongs relative to a transaction commit.

Observers receive publisher module, subscriber module, stable identities, order, duration, error, and panic state without the event payload. They can enrich each handler context and finish in reverse nesting order. Raw payloads are deliberately excluded from the observation contract.

Asynchronous delivery, retries, and durable publication are separate layers. The transactional protocol and at-least-once dispatcher are documented in outbox.md. They do not change this in-process contract or claim durability before an event is committed to application-owned storage.