ed

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2024 License: MIT Imports: 5 Imported by: 0

README

ed

Go Reference Go Test

Stablility

This module is tagged as v0, thus complies with Go's definition and rules about v0 modules (https://go.dev/doc/modules/version-numbers#v0-number). In short, it means that the API of this module may change without incrementing the major version number. Each releasable version will simply increment the patch number. Given the surface area of this module is quite small, this should not be a huge issue if used in production code.

Documentation

Overview

Package ed implements an event dispatcher that is meant to allow codebases to organize concerns around events. Instead of placing all concerns about a particular business logic event in one function, this package allows you to declare events by type and dispatach those events to other code so that other concerns may be maintained in separate areas of the codebase.

Stability

This module is tagged as v0, thus complies with Go's definition and rules about v0 modules (https://go.dev/doc/modules/version-numbers#v0-number). In short, it means that the API of this module may change without incrementing the major version number. Each releasable version will simply increment the patch number.

Given the surface area of this module is quite small, this should not be a huge issue if used in production code.

Register/Dispatch events

ed uses Go's own type system to dispatch events to the correct event handlers.

By simply registering an event handler that accepts an event of a particular type:

ed.Register(func(ctx context.Context, event MyEvent) error {
    fmt.Println("neat! got an event!")
    return nil
})

That handler will be triggered when an event of the same type is dispatched from somewhere else in the program:

err := ed.Dispatch(ctx, MyEvent{Handy: "Info"}) // neat! got an event!

Because it is Go's type system, event handlers can also be registered against interfaces instead of just concrete types. Thus, creating an event handler that will be triggered for all events is as simple as:

ed.Register(func(ctx context.Context, event any) error {
    fmt.Println("give me all the events!")
    return nil
})
type CreateEvent interface { IsCreatedEvent() }
ed.Register(func(ctx context.Context, createEvent CreateEvent) error {
    fmt.Println("give me all events about stuff getting created.")
    return nil
})

Goals

Overall, this package's goal is to mostly be an aid in allowing application business logic to remain clear of ancillary conerns/activities that might otherwise pile up in certain code areas.

For example, as a SaaS company grows, a lot of cross-cutting concerns can build up around a customer signing up for a service. In a monolithic codebase/service, the code that handles a new customer signup would become full of "do this check", "send this data to marketing systems", "screen this signup for abusive behavior" logic.

This is where ed is meant to be: taking all of those concerns/side-effects that are ancillary to the core act of signing up a user, and placing them in there own code areas and allowing them to be communicated with via events.

Non-goals

This package is not trying to be a job queue, or a messaging system. The dispatching of events is done synchronously and the events are designed to allow errors to be returned so that use-cases like synchronous validation are supported.

That being said, a common use-case might be to use this package to allow the publishing of events/messages to a Kafka/SQS-like system, an event handler:

ed.Register(func(ctx context.Context, event UserSignupEvent) error {
    return kafka.Send(ctx, KafkaMessage{Topic: "send_welcome_email", Body: json.MustEncode(event)})
})

Then in your main business logic for user signups:

func UserSignupEndpoint(ctx context.Context, request UserSignupRequest) (UserSignupResponse, error) {
    // check email not in use
    // check password strength
    err := db.SaveUser(...)
    if err != nil { /* ... */ }
    if err := ed.Dispatch(ctx, UserSignupEvent{/* ... */}); err != nil {
        return UserSignupResponse{}, err
    }
    // The welcome email was sent!
    // ...
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Dispatch

func Dispatch[E any](ctx context.Context, event E) error

Dispatch will send the provided event to all registered handlers and wrappers and allow them to return an error if necessary.

func Register

func Register[E any](handler Handler[E])

Register binds a Handler to a particular event by it's event type (E). The registered event type may also be an interface, to allow for capturing multiple types in one handler.

func Using

func Using[E any](r *Dispatcher) interface {
	// Wrap does the equivalent of [Wrap] on an explicit [Dispatcher].
	Wrap(wrapper Wrapper[E])

	// Register does the equivalent of [Register] on an explicit [Dispatcher].
	Register(handler Handler[E])

	// Dispatch does the equivalent of [Dispatch] on an explict [Dispatcher].
	Dispatch(ctx context.Context, event E) error
}

Using allows an instance of Dispatcher to be used with a specific type. The returned value of this is not meant to be "long-lived" or stored anywhere; rather used ephemerally and called as a chain.

func Wrap

func Wrap[E any](wrapper Wrapper[E])

Wrap will allow a Wrapper function to be called before any Handler of a matching event. The use-case for wrapping tends to be things like observability (logging, metrics, tracing, etc.). Wrapper functions that match a particular Dispatch will all be called serially in the order they were setup.

Types

type Dispatcher

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

Dispatcher is the object that represents how events of particular types are routed to registered Handler and Wrapper.

type Handler

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

Handler is a function responsible for handling an event. Returning an error from a Handler function will cause the entire dispatch operation for a Dispatch() to be canceled and will return the error.

type Wrapper

type Wrapper[E any] func(ctx context.Context, event E, next func(context.Context) error) error

Wrapper is a function that will be called before an Handler is called for a particular Dispatch call. With each Dispatch call, zero or many Wrapper functions might be called, but they will all be guaranteed to be called serially. Once all wrapper functions have invoked their next(), the actual Handler functions will be invoked.

Jump to

Keyboard shortcuts

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