Endure [currently in beta]
Endure is an open-source (MIT licensed) plugin container with IoC and self-healing.
Features
- Supports structs and interfaces (see examples)
- Use graph to topologically sort, run, stop and restart dependent plugins
- Algorithms used: graph and double-linked list
- Support easy to add Middleware plugins
- Error reporting
- Automatically restart failing vertices
Installation
go get -u github.com/spiral/endure
Why?
Imagine you have an application in which you want to implement plugin system. These plugins can depend on each other (
via interfaces or directly). For example, we have 3 plugins: HTTP (to communicate with world), DB (to save the world)
and logger (to see the progress).
In this particular case, we can't start HTTP before we start all other parts. Also, we have to initialize logger first,
because all parts of our system need logger. All you need to do in Endure is to pass HTTP, DB and Logger structs
to the Endure and implement Endure interface. So, the dependency graph will be the following:
=======
First step is to initialize the endure container:
container, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel), endure.Visualize(endure.StdOut, ""))
Let's take a look at the endure.NewContainer():
- First arg here is the external logger. If you want to use your own logger, you can pass it as the first argument.
- Next arguments are optional and can be set using
Options. For example endure.Visualize(endure.StdOut, "") will
show you dot-compatible graph in the console. TODO: all options section.
Then we need to pass our structures as references to the RegisterAll or Register function.
err = container.RegisterAll(
&httpPlugin{},
&DBPlugin{},
&LoggerPlugin{},
)
if err != nil {
panic(err)
}
The order of plugins in the RegisterAll plugin does no matter.
Next we need to initialize and run our container:
err = container.Init()
if err != nil {
panic(err)
}
errCh, err := container.Serve()
if err != nil {
panic(err)
}
errCh is the channel with errors from the all Vertices. You can identify vertex by vertexID which is presented
in errCh struct. Then just process the events from the errCh:
for {
select {
case e := <-errCh:
println(e.Error.Err.Error()) // just print the error, but actually error processing could be there
er := container.Stop()
if er != nil {
panic(er)
}
return
}
}
Also Endure will take care of the restart failing vertices (HTTP, DB, Logger in example) with exponential backoff
mechanism.
The start will proceed in topological order (Logger -> DB -> HTTP), and the stop in reverse-topological order
automatically.
Endure main interface
package sample
type (
// This is the main Endure service interface which may be implemented to Start (Serve) and Stop plugin (OPTIONAL)
Service interface {
// Serve
Serve() chan error
// Stop
Stop() error
}
// Name of the service (OPTIONAL)
Named interface {
Name() string
}
// Provider declares the ability to provide dependencies to other plugins (OPTIONAL)
Provider interface {
Provides() []interface{}
}
// Collector declares the ability to accept the plugins which match the provided method signature (OPTIONAL)
Collector interface {
Collects() []interface{}
}
)
// Init is mandatory to implement
type Plugin struct{}
func (p *Plugin) Init( /* deps here */) error {
return nil
}
Order is the following:
Init() error - is mandatory to implement. In your structure (which you pass to Endure), you should have this method
as a receiver. It can accept as parameter any passed to the Endure structure (see samples) or interface (with
limitations).
Service - is optional to implement. It has 2 main methods - Serve which should run the plugin and return
initialized golang channel with errors, and Stop to shut down the plugin.
Provider - is optional to implement. It is used to provide some dependency if you need to extend your struct.
Collector - is optional to implement. It is used to mark a structure (vertex) as some struct dependency. It can
accept interfaces which implement the caller.
Named - is optional to implement. This is a special kind of interface which provides the name of the struct (
plugin, vertex) to the caller. Is useful in logger (for example) to know user-friendly plugin name.