command

package module
v1.1.4 Latest Latest
Warning

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

Go to latest
Published: May 1, 2020 License: MIT Imports: 2 Imported by: 0

README

Go Command Bus

A command bus to demand all the things.

Build Status Maintainability Test Coverage GoDoc

Installation

go get github.com/io-da/command

Overview

  1. Commands
  2. Handlers
  3. Error Handlers
  4. The Bus
    1. Tweaking Performance
    2. Shutting Down
    3. Available Errors
  5. Benchmarks
  6. Examples

Introduction

This library is intended for anyone looking to trigger application commands in a decoupled architecture.
The Bus provides the option to use workers (goroutines) to attempt handling the commands in non-blocking manner.
Clean and simple codebase. No reflection, no closures.

Getting Started

Commands

Commands are any type that implements the Command interface. Ideally they should contain immutable data.

type Command interface {
    ID() []byte
}
Handlers

Handlers are any type that implements the Handler interface. Handlers must be instantiated and provided to the bus on initialization.

type Handler interface {
    Handle(cmd Command) error
}
Error Handlers

Error handlers are any type that implements the ErrorHandler interface. Error handlers are optional (but advised) and provided to the bus using the bus.ErrorHandlers function.

type ErrorHandler interface {
    Handle(evt Event, err error)
}

Any time an error occurs within the bus, it will be passed on to the error handlers. This strategy can be used for decoupled error handling.

The Bus

Bus is the struct that will be used to trigger all the application's commands.
The Bus should be instantiated and initialized on application startup. The initialization is separated from the instantiation for dependency injection purposes.
The application should instantiate the Bus once and then use it's reference to trigger all the commands.
The order in which the handlers are provided to the Bus is always respected. Additionally a command may have multiple handlers.

Tweaking Performance

The number of workers for async commands can be adjusted.

bus.WorkerPoolSize(10)

If used, this function must be called before the Bus is initialized. And it specifies the number of goroutines used to handle async commands.
In some scenarios increasing the value can drastically improve performance.
It defaults to the value returned by runtime.GOMAXPROCS(0).

The buffer size of the async commands queue can also be adjusted.
Depending on the use case, this value may greatly impact performance.

bus.QueueBuffer(100)

If used, this function must be called before the Bus is initialized.
It defaults to 100.

Shutting Down

The Bus also provides a shutdown function that attempts to gracefully stop the command bus and all its routines.

bus.Shutdown()

This function will block until the bus is fully stopped.

Available Errors

Below is a list of errors that can occur when calling bus.HandleAsync or bus.Handle.

// command.ErrorInvalidCommand
// command.ErrorCommandBusNotInitialized
// command.ErrorCommandBusIsShuttingDown

if err := bus.Handle(&Command{}); err != nil {
    switch(err.(type)) {
        case command.ErrorInvalidCommand:
            // do something
        case command.CommandBusNotInitializedError:
            // do something
        case command.CommandBusIsShuttingDownError:
            // do something
        default:
            // do something
    }
}

Benchmarks

All the benchmarks are performed against batches of 1 million commands.
All the benchmarks contain some overhead due to the usage of sync.WaitGroup.
The command handlers use time.Sleep(time.Nanosecond * 200) for simulation purposes.

Benchmark Type Time
Sync Commands 531 ns/op
Async Commands 476 ns/op

Examples

Example Commands

A simple struct command.

type Foo struct {
    bar string
}
func (*Foo) ID() []byte {
    return []byte("FOO-UUID")
}

A string command.

type Bar string
func (Bar) ID() []byte {
    return []byte("BAR-UUID")
}
Example Handlers

A command handler that logs every command triggered.

type LoggerHandler struct {
}

func (hdl *LoggerHandler) Handle(cmd Command) error {
    log.Printf("command %T emitted", cmd)
    return nil
}

A command handler that listens to multiple command types.

type FooBarHandler struct {
}

func (hdl *FooBarHandler) Handle(cmd Command) error {
    // a convenient way to assert multiple command types.
    switch cmd := cmd.(type) {
    case *Foo, Bar:
        // handler logic
    }
    return nil
}
Putting it together

Initialization and usage of the exemplified commands and handlers

import (
    "github.com/io-da/command"
)

func main() {
    // instantiate the bus (returns *command.Bus)
    bus := command.NewBus()
    
    // initialize the bus with all of the application's command handlers
    bus.Initialize(
        // this handler will always be executed first
        &LoggerHandler{},
        // this one second
        &FooBarHandler{},
    )
    
    // trigger commands!
    bus.Handle(&Foo{})
    bus.Handle(Bar("bar"))
}

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

MIT

Documentation

Index

Constants

View Source
const (
	// InvalidCommandError is a constant equivalent of the ErrorInvalidCommand error.
	InvalidCommandError = ErrorInvalidCommand("command: invalid command")
	// BusNotInitializedError is a constant equivalent of the ErrorBusNotInitialized error.
	BusNotInitializedError = ErrorBusNotInitialized("command: the bus is not initialized")
	// BusIsShuttingDownError is a constant equivalent of the ErrorBusIsShuttingDown error.
	BusIsShuttingDownError = ErrorBusIsShuttingDown("command: the bus is shutting down")
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Bus

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

Bus is the only struct exported and required for the command bus usage. The Bus should be instantiated using the NewBus function.

func NewBus

func NewBus() *Bus

NewBus instantiates the Bus struct. The Initialization of the Bus is performed separately (Initialize function) for dependency injection purposes.

func (*Bus) ErrorHandlers

func (bus *Bus) ErrorHandlers(hdls ...ErrorHandler)

ErrorHandlers may optionally be provided. They will receive any error thrown during the command process.

func (*Bus) Handle

func (bus *Bus) Handle(cmd Command) error

Handle the command synchronously.

func (*Bus) HandleAsync

func (bus *Bus) HandleAsync(cmd Command) error

HandleAsync the command using the workers asynchronously.

func (*Bus) Initialize

func (bus *Bus) Initialize(hdls ...Handler)

Initialize the command bus.

func (*Bus) QueueBuffer

func (bus *Bus) QueueBuffer(queueBuffer int)

QueueBuffer may optionally be provided to tweak the buffer size of the async commands queue. This value may have high impact on performance depending on the use case. It can only be adjusted *before* the bus is initialized. It defaults to 100.

func (*Bus) Shutdown

func (bus *Bus) Shutdown()

Shutdown the command bus gracefully. *Async commands handled while shutting down will be disregarded*.

func (*Bus) WorkerPoolSize

func (bus *Bus) WorkerPoolSize(workerPoolSize int)

WorkerPoolSize may optionally be provided to tweak the worker pool size for async commands. It can only be adjusted *before* the bus is initialized. It defaults to the value returned by runtime.GOMAXPROCS(0).

type Command

type Command interface {
	ID() []byte
}

Command is the interface that must be implemented by any type to be considered a command.

type ErrorBusIsShuttingDown

type ErrorBusIsShuttingDown string

ErrorBusIsShuttingDown is used when commands are handled but the bus is shutting down.

func (ErrorBusIsShuttingDown) Error

func (e ErrorBusIsShuttingDown) Error() string

Error returns the string message of ErrorBusIsShuttingDown.

type ErrorBusNotInitialized

type ErrorBusNotInitialized string

ErrorBusNotInitialized is used when commands are handled but the bus is not initialized.

func (ErrorBusNotInitialized) Error

func (e ErrorBusNotInitialized) Error() string

Error returns the string message of ErrorBusNotInitialized.

type ErrorHandler

type ErrorHandler interface {
	Handle(cmd Command, err error)
}

ErrorHandler must be implemented for a type to qualify as an error handler.

type ErrorInvalidCommand

type ErrorInvalidCommand string

ErrorInvalidCommand is used when invalid commands are handled.

func (ErrorInvalidCommand) Error

func (e ErrorInvalidCommand) Error() string

Error returns the string message of ErrorInvalidCommand.

type Handler

type Handler interface {
	Handle(cmd Command) error
}

Handler must be implemented for a type to qualify as a command handler.

Jump to

Keyboard shortcuts

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