eventbus

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package eventbus is an in-process, synchronous event bus for decoupling domain packages that cannot import one another (avoiding import cycles).

A producer domain dispatches an event by (domain, action); consumer domains register handlers for the pairs they care about. The producer never imports the consumers — it only knows the event's name and payload — so dependencies flow one way. Handlers run synchronously on the dispatching goroutine.

Choose eventbus for internal, in-process reactions that should happen as part of the same call (and whose failure should surface to the caller). It is NOT a message broker: it offers no durability, async delivery, retries, or cross-service transport — for those, use servicekit's outbox + broker. The two are complementary: a domain change can both fire an eventbus event for in-process side effects and write to the outbox for reliable external delivery.

eventbus dispatches outside any transaction: handlers run after the producer's write. When you need a domain change and its reactions to commit atomically — or to survive a crash — use servicekit's outbox instead, which writes events in the same transaction as the domain change and delivers them reliably.

Usage

At startup, construct a Bus and let consumers Register handlers; a handler recovers the typed payload with Decode. Producers build an event with NewData (or MustData) and dispatch it with Call or Publish:

bus := eventbus.New(log)

type UserCreated struct{ ID string }

// Consumer side (wired once, never imports the producer):
bus.Register("user", "created", func(ctx context.Context, d eventbus.Data) error {
    ev, err := eventbus.Decode[UserCreated](d)
    if err != nil {
        return err
    }
    return mailer.SendWelcome(ctx, ev.ID)
})

// Producer side (only knows the event name and payload):
evt := eventbus.MustData("user", "created", UserCreated{ID: u.ID})
if err := bus.Call(ctx, evt); err != nil { // abort on first handler error
    return err
}

Dispatch semantics

  • Call — run handlers in registration order, stop at and return the first error. Use it when a side effect's failure must abort the producer.
  • Publish — run every handler regardless of individual failures and return their errors joined with errors.Join. Use it for independent, best-effort notifications.

A (domain, action) pair with no handlers is a no-op. The Bus is safe for concurrent use, and handlers are snapshotted before they run, so a handler may itself Register without deadlocking.

Events

Data is the event: a Domain, an Action, and opaque RawParams (JSON by convention) so the bus stays decoupled from any concrete payload type. NewData JSON-encodes params (nil for a payload-less event) and returns an error; MustData panics instead, for static types that cannot fail to encode. A handler reconstructs the payload with the generic Decode[T].

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decode

func Decode[T any](d Data) (T, error)

Decode unmarshals an event's JSON params into T. Handlers use it to recover the typed payload the producer encoded with NewData/MustData.

Types

type Bus

type Bus struct {
	// contains filtered or unexported fields
}

Bus routes events to the handlers registered for each (domain, action). It is safe for concurrent use: registration and dispatch are guarded by a mutex, and handlers are snapshotted before they run so a handler may itself register without deadlocking.

func New

func New(log *logger.Logger) *Bus

New constructs an empty Bus. log may be nil to disable logging.

func (*Bus) Call

func (b *Bus) Call(ctx context.Context, data Data) error

Call dispatches data to its handlers synchronously, stopping at and returning the first error. A pair with no handlers is a no-op. This is the right choice when a side effect's failure must abort the producer's operation.

func (*Bus) Publish

func (b *Bus) Publish(ctx context.Context, data Data) error

Publish dispatches data to every handler regardless of individual failures and returns their errors joined with errors.Join (nil when all succeed). Use it for independent, best-effort notifications where one handler's failure should not prevent the others from running.

func (*Bus) Register

func (b *Bus) Register(domain, action string, fn Func)

Register adds fn as a handler for the given domain and action. Multiple handlers may be registered for the same pair; they run in registration order. Registration typically happens once at construction time.

type Data

type Data struct {
	Domain    string
	Action    string
	RawParams []byte
}

Data is a single event passed between domains. Params are opaque bytes (JSON by convention) so the bus stays decoupled from any concrete type: the producer encodes, the consumer decodes, and neither needs the other's struct beyond the shared contract.

func MustData

func MustData(domain, action string, params any) Data

MustData is NewData that panics on a marshal error. Use it for static param types that cannot fail to encode (the common case), where an error would indicate a programming mistake rather than a runtime condition.

func NewData

func NewData(domain, action string, params any) (Data, error)

NewData builds an event for the given domain and action, JSON-encoding params into RawParams. Pass nil params for an event that carries no payload.

func (Data) String

func (d Data) String() string

String implements fmt.Stringer.

type Func

type Func func(ctx context.Context, data Data) error

Func is a handler registered for a (domain, action) pair and invoked when a matching event is dispatched. It runs synchronously on the dispatching goroutine; returning an error is surfaced to the producer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL