resourceeventbus

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 26, 2026 License: MIT Imports: 2 Imported by: 0

README

resource-eventbus

A minimal, type-safe in-process pub/sub for Go. Built on top of asaskevich/EventBus. Two usage modes:

  • Lightweight — declare TypedEvent[T] inline, no codegen.
  • Full DSL + codegen — write a DSL, run eventbus-gen, get a generated events package with metadata-aware Publish(data, opts...).

Both modes share the same underlying Bus interface, so they interoperate.

Install

# library
go get github.com/NameOfGoy/resource-eventbus

# generator CLI (only if you want codegen)
go install github.com/NameOfGoy/resource-eventbus/cmd/eventbus-gen@latest

Requires Go 1.23+ (generics + any alias).


Mode 1: Lightweight (no codegen)

Declare an event once, reuse it everywhere with full type safety:

package events

import resbus "github.com/NameOfGoy/resource-eventbus"

type UserCreated struct {
    ID   uint
    Name string
}

var UserCreatedEvent = &resbus.TypedEvent[UserCreated]{Topic: "user.created"}
// publisher
events.UserCreatedEvent.PublishAsync(events.UserCreated{ID: 1, Name: "alice"})

// subscriber (registered once at startup)
err := events.UserCreatedEvent.SubscribeAsync(func(u events.UserCreated) {
    // handle...
}, false)

SubscribeAsync(handler, transactional):

  • transactional=false — handler can run concurrently with others on the same topic.
  • transactional=true — handlers on this topic are serialized.

Mode 2: DSL + codegen

Write a dsl.go declaring an EventsDSL() function (see _examples/dsl.go):

package events

import (
    "github.com/NameOfGoy/resource-eventbus/generator"
    "your-project/models"
)

func EventsDSL() []generator.ResourceDef {
    return []generator.ResourceDef{
        {
            Resource: "device",
            Operations: []generator.OpDef{
                {Op: "created", Struct: models.Device{}},
                {Op: "updated", Struct: models.Device{}},
                {Op: "status_changed", Struct: models.DeviceStatus{}},
            },
        },
        {
            Resource: "alarm",
            Operations: []generator.OpDef{
                {Op: "triggered", Struct: models.Alarm{}},
                {Op: "resolved",  Struct: models.Alarm{}},
            },
        },
    }
}

Run the generator:

eventbus-gen -dsl=./events/dsl.go
# emits events/events.go (topic constants) + events/typed.go (TypedEvent[T] + factories)

Use the generated API (note: this TypedEvent[T] carries metadata):

import (
    "your-project/events"
    "your-project/models"
    "github.com/NameOfGoy/resource-eventbus/generator"
)

// publish, with metadata via functional options
events.Device().Created().Publish(
    models.Device{ID: "d-1", Name: "rack-a-cell-3"},
    generator.WithSource("device-service"),
    generator.WithTraceID("trace-abc"),
)

// async publish (returns immediately)
events.Device().Updated().PublishAsync(models.Device{ID: "d-1", Name: "renamed"})

// subscribe, handler also receives metadata
events.Device().Created().SubscribeAsync(
    func(d models.Device, meta *generator.EventMetadata) {
        fmt.Printf("[%s] from=%s id=%s\n", meta.TraceID, meta.Source, d.ID)
    },
    false,
)

CLI flags:

eventbus-gen -dsl=./events/dsl.go            # generate
eventbus-gen -dsl=./events/dsl.go -verbose   # show DSL info + stats
eventbus-gen -dsl=./events/dsl.go -dry-run   # stats only, no files written

Naming conventions:

  • DSL: snake_case (device_cell, status_changed)
  • Generated: UpperCamelCase (DeviceCellStatusChanged = "device_cell.status_changed")

Conflict handling: if a resource's UpperCamel name matches a struct already defined in the DSL file's package, the factory function is renamed ResourceFoo() instead of Foo() to avoid shadowing the struct.

Add the generated files to .gitignore:

events/events.go
events/typed.go

Testing

Replace the singleton bus per test for isolation:

import (
    resbus "github.com/NameOfGoy/resource-eventbus"
    asaskbus "github.com/asaskevich/EventBus"
)

func TestMyHandler(t *testing.T) {
    resbus.SetBus(asaskbus.New())
    // ... your test ...
}

Why this exists

Most projects end up either:

  • Reaching for a heavyweight message broker for in-process work.
  • Hand-rolling chan + sync.Map and getting subscription lifecycle wrong.

resource-eventbus stays small: one Bus interface, one generic TypedEvent[T], plus an optional DSL/codegen flow for teams that want metadata-rich events without writing the boilerplate by hand.

License

MIT — see LICENSE.

Documentation

Overview

Package resourceeventbus is a tiny, type-safe wrapper around an in-process publish/subscribe bus. It exposes:

  • Bus: a minimal pub/sub interface (Publish, Subscribe[Async|Once], Unsubscribe, HasCallback, WaitAsync).
  • A process-wide singleton accessible via GetBus / SetBus.
  • TypedEvent[T]: a generic wrapper that pins a topic to a payload type so callers don't have to deal with stringly-typed topics or any.

The default implementation wraps github.com/asaskevich/EventBus. The underlying library is interchangeable via SetBus(), which is useful in tests.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetBus

func SetBus(b Bus)

SetBus replaces the process-wide Bus. Mainly for tests, where you want a fresh isolated bus per test case.

Types

type Bus

type Bus interface {
	Publish(topic string, args ...any)
	Subscribe(topic string, fn any) error
	SubscribeAsync(topic string, fn any, transactional bool) error
	SubscribeOnce(topic string, fn any) error
	SubscribeOnceAsync(topic string, fn any) error
	Unsubscribe(topic string, handler any) error
	HasCallback(topic string) bool
	WaitAsync()
}

Bus is the minimal pub/sub surface this library promises. It matches the shape of asaskevich/EventBus on purpose so consumers can swap in any compatible implementation.

func GetBus

func GetBus() Bus

GetBus returns the process-wide Bus, creating the default one on first use.

type TypedEvent

type TypedEvent[T any] struct {
	Topic string
}

TypedEvent ties a topic string to a single payload type, so publishers and subscribers don't have to remember the wire shape themselves.

Typical usage:

type UserCreated struct{ ID uint }
var UserCreatedEvent = &resourceeventbus.TypedEvent[UserCreated]{Topic: "user.created"}

// publisher
UserCreatedEvent.PublishAsync(UserCreated{ID: 42})

// subscriber
UserCreatedEvent.SubscribeAsync(func(p UserCreated) { ... }, false)

func (*TypedEvent[T]) HasCallback

func (e *TypedEvent[T]) HasCallback() bool

HasCallback reports whether at least one subscriber is attached.

func (*TypedEvent[T]) Publish

func (e *TypedEvent[T]) Publish(data T)

Publish fires the event synchronously on the configured topic.

func (*TypedEvent[T]) PublishAsync

func (e *TypedEvent[T]) PublishAsync(data T)

PublishAsync fires the event from a fresh goroutine. Useful when the publishing call site shouldn't block on subscriber work.

func (*TypedEvent[T]) Subscribe

func (e *TypedEvent[T]) Subscribe(handler func(T)) error

Subscribe attaches a sync handler. Errors come from the underlying bus (typically: handler is not a function with the right signature).

func (*TypedEvent[T]) SubscribeAsync

func (e *TypedEvent[T]) SubscribeAsync(handler func(T), transactional bool) error

SubscribeAsync attaches an async handler. transactional=true serializes async handlers; false runs them concurrently.

func (*TypedEvent[T]) SubscribeOnce

func (e *TypedEvent[T]) SubscribeOnce(handler func(T)) error

SubscribeOnce attaches a handler that fires exactly once.

func (*TypedEvent[T]) SubscribeOnceAsync

func (e *TypedEvent[T]) SubscribeOnceAsync(handler func(T)) error

SubscribeOnceAsync attaches an async handler that fires exactly once.

func (*TypedEvent[T]) Unsubscribe

func (e *TypedEvent[T]) Unsubscribe(handler func(T)) error

Unsubscribe removes a previously registered handler. The handler reference must match what was passed to Subscribe* (functions compare by identity).

Directories

Path Synopsis
示例 DSL 文件.
示例 DSL 文件.
cmd
eventbus-gen command
eventbus-gen 把 DSL 文件 (一个声明 EventsDSL() []generator.ResourceDef 的 .go) 转成两份强类型源:
eventbus-gen 把 DSL 文件 (一个声明 EventsDSL() []generator.ResourceDef 的 .go) 转成两份强类型源:
Package generator 提供 eventbus 的 DSL 类型与代码生成器.
Package generator 提供 eventbus 的 DSL 类型与代码生成器.

Jump to

Keyboard shortcuts

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