boot

package module
v1.0.0-rc.1 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2022 License: MIT Imports: 13 Imported by: 7

README

Commit tag github action go report

boot-go accentuate component-based development (CBD).

This is an opinionated view of writing modular and cohesive Go code. It emphasizes the separation of concerns by loosely coupled components, which communicate with each other via interfaces and events. The goal is to support writing maintainable code on the long run, while taking the little more complexity compared to the well-defined standard library into account.

boot-go provided features are:

  • code decoupling
  • configuration handling
  • dependency injection

boot stack

boot-go was primarily designed to build opinionated frameworks and bundle them as a stack. So every developer or company can choose to use the default stack, a shared stack or rather create a new one. Stacks should be build with one specific purpose in mind for building a microservice, ui application, web application, data analytics application and so on. As an example, a web application boot stack could contain a http server component, a sql database component, a logging and a web application framework.

component

Everything in boot-go starts with a component. They are key fundamental in the development and can be considered as an elementary build block. The essential concept is to get all the necessary components functioning with as less effort as possible. Therefore, components must always provide a default configuration, which uses the most common settings. As an example, a http server should always start using port 8080, unless the developer specifies it. Or a postgres component should try to connect to localhost:5432 when there is no database url provided.

A component should be fail tolerant, recoverable, agnostic and decent.

Facet Meaning Example
fail tolerant Don't stop processing on errors. A http request can still be processed, even when the metrics server is not available anymore.
recoverable Try to recover from errors. A database component should try to reconnect after losing the connection.
agnostic Behave the same in any environment. A key-value store component should work on a local development machine the same way as in a containerized environment.
decent Don't overload the developer with complexity. Keep the interface and events as simple as possible. It's better to build three smaller but specific components then one general with increased complexity. Less is often more.

Simple Example

The hello component is a very basic example. It contains no fields or provides any interface to interact with other components. The component will just print the 'Hello World' message to the console.

package main

import (
	"github.com/boot-go/boot"
	"log"
)

// init() registers a factory method, which creates a hello component.
func init() {
	boot.Register(func() boot.Component {
		return &hello{}
	})
}

// hello is the simplest component.
type hello struct{}

// Init is the initializer of the component.
func (c *hello) Init() {
	log.Printf("boot-go says > 'Hello World'\n")
}

// Start the example and exit after the component was completed.
func main() {
	boot.Go()
}

Component wiring

This example shows how components get wired automatically with dependency injection. The server component starts at :8080 by default, but the port is configurable by setting the environment variable HTTP_SERVER_PORT.

package main

import (
	"github.com/boot-go/boot"
	"github.com/boot-go/stack/server/httpcmp"
	"io"
	"net/http"
)

// init() registers a factory method, which creates a hello component
func init() {
	boot.Register(func() boot.Component {
		return &hello{}
	})
}

// hello is a very simple http server example.
// It requires the Eventbus and the httpcmp.Server component. Both components
// are injected by the boot framework automatically
type hello struct {
	Eventbus boot.EventBus  `boot:"wire"`
	Server   httpcmp.Server `boot:"wire"`
}

// Init is the constructor of the component. The handler registration takes place here.
func (h *hello) Init() {
	// Subscribe to the registration event
	h.Eventbus.Subscribe(func(event httpcmp.InitializedEvent) {
		h.Server.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
			io.WriteString(writer, "boot-go says: 'Hello World'\n")
		})
	})
}

// Start the example and test with 'curl localhost:8080'
func main() {
	boot.Go()
}

Configuration

Configuration values can also be automatically injected with arguments or environment variables at start time. The value from USER will be used in this example. If the argument --USER madpax is not set and the environment variable is not defined, it is possible to specify the reaction whether the execution should stop with a panic or continue with a warning.

package main

import (
	"github.com/boot-go/boot"
	"log"
)

// hello is still a simple component.
type hello struct{
	Out string `boot:"config,key:USER,default:madjax"` // get the value from the argument list or environment variable. If no value could be determined, then use the default value `madjax`.
}

// init() registers a factory method, which creates a hello component and returns a reference to it.
func init() {
	boot.Register(func() boot.Component {
		return &hello{}
	})
}

// Init is the initializer of the component.
func (c *hello) Init() {
	log.Printf("boot-go says > 'Hello %s'\n", c.Out)
}

// Start the example and exit after the component was completed
func main() {
	boot.Go()
}

Examples

More examples can be found in the tutorial repository.

Documentation

Index

Constants

View Source
const (
	// DefaultName is used when registering components without an explicit name.
	DefaultName = "default"
)

Variables

View Source
var (
	// Logger contains a debug, info, warning and error logger, which is used for fine-grained log
	// output. Every logger can be muted or unmuted separately.
	Logger struct {
		Debug *log.Logger
		Info  *log.Logger
		Warn  *log.Logger
		Error *log.Logger
	}
)

Functions

func Go

func Go() error

Go the boot component framework. This starts the execution process.

func Override

func Override(create func() Component)

Override a default factory function.

func OverrideName

func OverrideName(name string, create func() Component)

OverrideName overrides a factory function with the given name.

func QualifiedName

func QualifiedName(v any) string

QualifiedName returns the full name of a struct, function or a simple name of a primitive.

func Register

func Register(create func() Component)

Register a default factory function.

func RegisterName

func RegisterName(name string, create func() Component)

RegisterName registers a factory function with the given name.

func Shutdown

func Shutdown()

Shutdown boot-go instance. All components will be stopped. This is equivalent with issuing a SIGTERM on process level.

func Split

func Split(s, sep, quote string) []string

Split returns the tokens separated by sep and ignores content in the quotes. Unfortunately, the regex can't be used to split the string, because Go has limitations.

func Test

func Test(mocks ...Component) error

Test one or more specific components within a unit test.

Types

type Component

type Component interface {
	// Init initializes data, set the default configuration, subscribe to events
	// or performs other kind of configuration.
	Init() error
}

Component represent functional building blocks, which solve one specific purpose. They should be fail tolerant, recoverable, agnostic and decent.

fail tolerant: Don't stop processing on errors. Example: A http request can still be processed, even when the logging server is not available anymore.

recoverable: Try to recover from errors. E.g. a database component should try to reconnect after lost connection.

agnostic: Behave the same in any environment. E.g. a key-value store component should work on a local development machine the same way as in a containerized environment.

decent: Don't overload the developer with complexity. E.g. keep the interface and events as simple as possible. Less is often more.

type DependencyInjectionError

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

DependencyInjectionError contains a detail description for the cause of the injection failure

func (*DependencyInjectionError) Error

func (e *DependencyInjectionError) Error() string

type EventBus

type EventBus interface {
	// Subscribe subscribes to a message type.
	// Returns error if handler fails.
	Subscribe(handler any) error
	// Unsubscribe removes handler defined for a message type.
	// Returns error if there are no handlers subscribed to the message type.
	Unsubscribe(handler any) error
	// Publish executes handler defined for a message type.
	Publish(event any) (err error)
	// HasMessageHandler returns true if exists any handler subscribed to the message type.
	HasMessageHandler(event any) bool
}

EventBus provides the ability to decouple components. It is designed as a replacement for direct methode calls, so components can subscribe for messages produced by other components.

func NewTestableEventBus

func NewTestableEventBus() EventBus

NewTestableEventBus can be used for unit testing.

type Flag

type Flag string

Flag describes a special behaviour of a component

const (
	// StandardFlag is set when the component is started in unit test mode.
	StandardFlag Flag = "standard"
	// UnitTestFlag is set when the component is started in unit test mode.
	UnitTestFlag Flag = "unitTest"
	// FunctionalTestFlag is set when the component is started in functional test mode.
	FunctionalTestFlag Flag = "functionalTest"
)

type Phase

type Phase uint8

Phase describes the status of the boot-go instance

const (
	// Initializing is set directly after the application started.
	// In this phase it is safe to subscribe to events.
	Initializing Phase = iota
	// Booting is set when starting the boot framework.
	Booting
	// Running is set when all components were initialized and started
	Running
	// Stopping is set when all components are requested to stop their service.
	Stopping
	// Exiting is set when all components were stopped.
	Exiting
)

func (Phase) String

func (p Phase) String() string

type Process

type Process interface {
	Component
	// Start is called as soon as all boot.Component components are initialized. The call should be
	// blocking until all processing is completed.
	Start() error
	// Stop is called to abort the processing and clean up resources. Pay attention that the
	// processing may already be stopped.
	Stop() error
}

Process is a Component which has a processing functionality. This can be anything like a server, cron job or long-running process.

type Runtime

type Runtime interface {
	HasFlag(flag Flag) bool
}

Runtime is a standard component, which is used to alternate the component behaviour at runtime.

type State

type State int

State is used to describe the current state of a component instance

const (
	// Created is set directly after the component was created by the provided factory
	Created State = iota
	// Initialized is set after the component Init() function was called
	Initialized
	// Started is set after the component Start() function was called
	Started
	// Stopped is set after the component Stop() function was called
	Stopped
)

Jump to

Keyboard shortcuts

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