Categorygithub.com/mkock/bootseq
repositorypackage
2.0.0-beta.2+incompatible
Repository: https://github.com/mkock/bootseq.git
Documentation: pkg.go.dev

# README

bootseq

GoDoc License GoReportCard

Package bootseq provides a light-weight, general-purpose boot sequence manager with separate startup/shutdown phases, real-time progress reports, cancellation and a simple mechanism that allows for easy control over execution order and concurrency.

Compatibility: Go 1.7+

Installation

This package supports Go Modules. Simply run:

go get github.com/mkock/bootseq/v2

to get the latest tagged version.

Quickstart

sequence := bootseq.New("My Boot Sequence")
sequence.Register("some-service", someServiceUp, someServiceDown)
sequence.Register("other-service", otherServiceUp, otherServiceDown)
sequence.Register("third-service", thirdServiceUp, thirdServiceDown).After("other-service")
agent, err := sequence.Agent()
up := agent.Up(context.Background())
up.Wait()

// Your application is now ready!

down := agent.Down(context.Background())
down.Wait()

Preamble

This README describes v2.

Although v1 is still available, it has been deprecated due to its quirky syntax for formulating execution order which, to be honest, is much better handled via simple method calls. V2 greatly improves upon this.

Introduction

A "boot sequence" is one or more Services to execute. Each service is a name and two functions, one for booting up (the startup phase) and one for shutting down (the shutdown phase). These are registered with the Manager.

The practicalities of executing the two up/down phases is handled by an Agent, which can be instantiated directly from the Manager once Service registration has completed.

Usage

Start by analyzing your boot sequence. Some services must run in a certain order (ie. you must establish a database connection before you can preload data for an in-memory cache), and some services can run concurrently when they don't depend on each other.

Next, write two functions per service: a startup function, and a shutdown function. If you don't need both, you can use bootseq.Noop as a replacement, it simply does nothing.

Once your functions are ready, you just need to register them under a meaningful name and then define their order of execution by calling Service.After("name-of-dependent-service").

If your application has multiple packages that need to do some processing during the startup and shutdown phases of the boot sequence, you can register them in the init functions of the respective packages.

Details

Progress reports

Once a boot sequence is in progress, you may call either Agent.Wait() or Agent.Progress() on the agent. You'll get an error if you call both.

Agent.Wait() will block while the boot sequence is running. It returns when the sequence has completed, or if an error was raised during execution.

Agent.Progress(), on the other hand, will return a channel of Progress structs. You can then iterate over each Progress struct to receive progress updates as they occur. Although these progress reports don't tell you how far you are in the boot sequence, you can easily devise your own progress indicator by getting the total number of services from Manager.ServiceCount() combined with a counter that is incremented by one for every Progress report received. Progress reports also serve as error handlers.

Progress reports are simple structs containing the name of the executed service and an error (which is nil for successful execution):

type Progress struct {
	Service string
	Err     error
}

Due to the fact that execution steps may be cancelled or time out due to their associated context, the reported error can be of type context.Canceled or context.DeadlineExceeded. It can also be of any type returned by your Service functions.

Note: you will receive exactly one Progress report per registered Service, plus one extra that indicates the end of the boot sequence. The last Progress report will always have an empty Service name.

Cancellation and Errors

Any boot sequence can be cancelled by calling Agent.Up() with a context that can be cancelled. A call to Context.Done() is checked before each non-concurrent Service execution, so a Service that is in progress will finish before stopping. For sequences where multiple Services are running concurrently, the Agent will wait for all of them to finish before stopping.

If, for example, Service A, B and C run sequentially - one by one - and there is an error in B, that means that A would still have completed, but as the Agent is not waiting for any other concurrent Services, execution can halt immediately and C won't run.

In another example, Service A, B and C run concurrently, and Service D runs after C. If B fails, A and C will continue to run to completion, but execution stops afterwards, and D won't run.

Builtin Limitations

  • A Manager cannot contain more than 65535 Services
  • An Agent cannot contain more than 65535 priorities

A panic is raised if any of these limitations are breached.

Feature Wish List

  • Optional injection of logger after instantiation
  • Proper shutdown on panics and timeouts
  • Method for unregistering services

FAQ

What happens if I register Service B before Service A, when B depends on A?

No problem. Service dependencies aren't resolved until Agent instantiation. You are free to register Services via the init functions of your various packages - in fact, this is the intended use case.

Can more Services be added to the Manager after Agent instantiation?

While it's possible to continue adding more Services to the Manager after Agent instantiation, they will not be part of the Agent's boot sequence. You'll have to re-instantiate it.

What if registered Services have cyclic dependencies?

These will be detected during Agent instantiation, and you'll receive an error if this happens.

What are the dependencies for this package?

None. Just stdlib and golang.org/x/sync.

Contributing

Contributions are welcome in the form of well-explained PR's along with some beautiful unit tests ;-)

About v1

The v1 version of this package is deprecated and unsupported. In case you need to use it anyway, the documentation for v1 is here.

License

This software package is released under the MIT License.