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+
This package supports Go Modules. Simply run:
go get github.com/mkock/bootseq/v2
to get the latest tagged version.
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()
err = agent.Up(context.Background(), nil)
// Your application is now ready!
err = agent.Down(context.Background(), nil)
// Your application has now been shut down!
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.
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.
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.
When calling Agent.Up()
or Agent.Down()
, provide a function callback as the second argument in order to receive
progress reports during execution. The callback is called every time a Service
operation has completed, and once more
when the entire boot sequence has completed. For boot sequences with n Services
, the callback would get called
n+1 times. If one of the Services
return an error, execution stops at that point and no further calls are made to
the callback.
The callback function must have this signature: func(Progress)
. Progress
is a simple struct:
struct Progress {
Service string
Err error
}
It keeps the name of the executed Service
and an error (which may be nil). The final Progress
received which marks
the end of the boot sequence, always contain an empty Service
name, ie. an empty string.
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.
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.
- 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.
- Optional injection of logger after instantiation
- Log execution times for individual services
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.
Contributions are welcome in the form of well-explained PR's along with some beautiful unit tests ;-)
The v1 version of this package is deprecated and unsupported. In case you need to use it anyway, the documentation for v1 is here.
This software package is released under the MIT License.