events

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package events is a typed, in-process event bus: no `any` at the call site, no type assertion in the handler, and the failure modes handled.

Use it to decouple a side effect from the thing that caused it: checkout publishes OrderPlaced, and the audit log, the email sender and the usage meter each subscribe without the checkout code importing any of them.

Three things it does that a channel and a goroutine do not:

  • A handler panic is contained. One bad subscriber cannot take down the publisher, which in an HTTP service is the request.
  • Async handlers get a DETACHED context. Handing a handler the request context means the side effect is cancelled when the client disconnects — the notification silently never sends.
  • Publish waits for the synchronous handlers and returns their errors, so an effect that MUST happen can be made to block the write that caused it.

What it is not: durable. The process dying loses every unhandled event. When the effect must survive a crash, write it to a table in the same transaction as the state change and publish from there.

Example

Subscribers are typed: no `any` at the call site and no assertion in the handler.

package main

import (
	"context"
	"fmt"

	"github.com/IncepTools/go/src/platform/events"
)

// OrderPlaced is published by checkout. Nothing in checkout imports the
// audit log, the mailer or the usage meter.
type OrderPlaced struct {
	OrderID string
	Total   int64
}

func main() {
	bus := events.New()

	events.On(bus, "orders.audit", events.Sync,
		func(_ context.Context, e OrderPlaced) error {
			fmt.Println("audit:", e.OrderID)
			return nil
		})
	events.On(bus, "orders.meter", events.Sync,
		func(_ context.Context, e OrderPlaced) error {
			fmt.Println("meter:", e.Total)
			return nil
		})

	if err := events.Publish(context.Background(), bus, OrderPlaced{
		OrderID: "9c1e", Total: 1200,
	}); err != nil {
		panic(err)
	}
}
Output:
audit: 9c1e
meter: 1200

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func On

func On[T any](b *Bus, name string, mode Mode, h Handler[T])

On subscribes to events of type T. name identifies the subscriber in logs, which is the difference between "an event handler failed" and a line you can act on.

func Publish

func Publish[T any](ctx context.Context, b *Bus, event T) error

Publish dispatches an event.

It returns the joined errors of the SYNCHRONOUS subscribers only. Async errors are logged by the bus, because by definition nobody is waiting for them.

Example (AsyncFailuresDoNotReachThePublisher)

An async handler's failure never reaches the publisher: the side effect must not fail the request that caused it. The bus logs it instead.

package main

import (
	"context"
	"fmt"

	"github.com/IncepTools/go/src/platform/events"
)

// OrderPlaced is published by checkout. Nothing in checkout imports the
// audit log, the mailer or the usage meter.
type OrderPlaced struct {
	OrderID string
	Total   int64
}

func main() {
	bus := events.New()
	events.On(bus, "orders.mail", events.Async,
		func(context.Context, OrderPlaced) error {
			return fmt.Errorf("the mail server refused the connection")
		})

	err := events.Publish(context.Background(), bus, OrderPlaced{OrderID: "9c1e"})
	bus.Wait()

	fmt.Println("publisher saw:", err)
}
Output:
publisher saw: <nil>

Types

type Bus

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

Bus dispatches events to subscribers. The zero value is usable.

func New

func New() *Bus

New returns an empty bus.

func (*Bus) OnShutdown

func (b *Bus) OnShutdown(ctx context.Context) error

OnShutdown implements lifecycle.Shutdowner, so a bus registered as a hook drains itself on the way down.

func (*Bus) Subscribers

func (b *Bus) Subscribers() map[string][]string

Subscribers lists the handler names registered for each event type, for diagnostics.

func (*Bus) Wait

func (b *Bus) Wait()

Wait blocks until every async handler started so far has finished.

Call it from a shutdown hook. Without it, a SIGTERM during a burst of publishes drops the side effects that were still in flight.

type Handler

type Handler[T any] func(ctx context.Context, event T) error

Handler reacts to one event.

type Mode

type Mode int

Mode selects how a subscriber runs.

const (
	// Async runs the handler in its own goroutine with a detached context.
	// Publish does not wait for it and never sees its error.
	//
	// This is the right default for a side effect: an audit write must not
	// slow down, or fail, the request that produced it.
	Async Mode = iota
	// Sync runs the handler inline. Publish waits and collects the error.
	//
	// Use it only when the publisher genuinely must not proceed if the
	// handler fails — and prefer a direct call in that case, because a
	// required dependency hidden behind an event bus is harder to follow than
	// a function call.
	Sync
)

Jump to

Keyboard shortcuts

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