plugger/v3
is a minimalist Go plugin manager featuring type-safe handling of
functions and interfaces (“symbols”) exposed by plugins. Type safety is checked
at compile time, thanks to Go Generics. Plugins usually are realized as packages
exposing certain well-defined functions or interfaces by registering these.
Plugin packages can be statically linked to or dynamically loaded by a Go
application binary.
Applications then can retrieve, for instance, a list of the exposed plugin functions (“symbols”) of a specific type and then call all of these exposed plugin functions one after another – and without having to explicitly maintain a dedicated list of package functions to call in code. As practice shows, such lists quickly tend to get forgotten when adding new plugins.
plugger/v3
ensures a well-defined order of the symbols of the same type, where
the symbols are either sorted lexicographically based on plugin names or
optionally using ”placement hints”. This supports such use cases where some of
the plugins might actually build upon the results from plugins that were invoked
earlier.
Another use case is an application retrieving the exposed symbol for only a particular single named plugin and invoking only this particular plugin.
Finally, plugger/v3
is safe for concurrent use (as opposed to v0/v2 that are
not).
To add plugger/v3
to your Go module as a dependency:
go get github.com/thediveo/go-plugger/v3@latest
Just three steps...
First, define a type for the symbol you want to expose by your plugins; this
must be either a function or interface (but not a pure type-constraining
interface). This type will then be used plugger
to manage different exposed
symbol types in separate so-called "groups".
type pluginFn func() string
Define this type only in one place and then import it into your plugins as well as in the places where you need to work with the exposed symbol(s). Using a dedicated package just for the exposed symbol type might at first look like overkill but is your friend against import cycles.
Second, in your plugins, register (expose) the respective pluginFn
implementations by fetching the group object for your specific symbol type and
then calling Register
on it.
import "github.com/thediveo/go-plugger/v3"
func init() {
plugger.Group[pluginFn]().Register(MyPluginFn)
}
func MyPluginFn() string { return "foo" }
Please note that plugger/v3
defaults to deriving the plugin name from the
package name where Register
is called.
Finally, when you want to invoke the registered symbols, grab the group object for your specific symbol type and then range over the group's exposed symbols.
import (
"github.com/thediveo/go-plugger/v3"
// ...
// don't forget to underline-import your (static) plugins!
)
func main() {
pluginFnGroup := plugger.Group[pluginFn]()
for _, pluginFn := range pluginFnGroup.Symbols() {
fmt.Println(pluginFn())
}
}
Please see also example/dynplug
for a working example.
- make sure your plugin has a
main
package with an emptymain
function. - build your plugin shared object using
go build -tags plugger_dynamic -buildmode=plugin ...
- Please don't forget to specify the
plugger_dynamic
build tag/constraint; otherwise, trying to automatically discover and load plugins usingdyn.Discover
will panic with a notice to enable theplugger_dynamic
build tag.
- Please don't forget to specify the
- in you application, call
dyn.Discover
to discover plugins in a specific directory (and sub directories) and to load them.
In plugger/v3
, groups now correspond with exactly one symbol type, whereas
v0/v2 allowed to register multiple symbols for the same plugin in the same
group. In v3, simply use multiple and now type-safe groups as needed, one for
each type of exposed symbol.
As one benefit, exposed symbols are now inherently nameless from the perspective of the plugin manager, so no more need to deal with them. And another benefit is that groups are also nameless too, but instead they are now (symbol) typed.
In v3, exposed symbols are simply registered using their corresponding type-safe
and name-less group, and with the only options available being WithName(name)
and WithPlacement(hint)
.
// v3:
plugger.Group[fooFn]().Register(foo)
// before, v0:
// plugger.RegisterPlugin(&plugger.PluginSpec{
// Group: "group",
// Name: "plug1",
// Symbols: []plugger.Symbol{foo},
// })
// before, v2:
// plugger.Register(plugger.WithName("plug1"),
// plugger.WithGroup("group"), plugger.WithSymbol(foo))
Sometimes, unit tests need a well-defined isolated plugin group configuration.
For this, PluginGroup[T]
objects as returned by Group[T]()
can now be backed
up and restored using PluginGroup[T].Backup()
and PluginGroup[T].Restore()
.
Additionally, PluginGroup[T].Clear()
resets a plugin group to its initial
empty state.
The included go-plugger.code-workspace
defines the following tasks:
-
View Go module documentation task: installs
pkgsite
, if not done already so, then startspkgsite
and opens VSCode's integrated ("simple") browser to show the go-plugger/v2 documentation. -
Build workspace task: builds all, including the shared library test plugin.
-
Run all tests with coverage task: does what it says on the tin and runs all tests with coverage.
- pksite service: auxilliary task to run
pkgsite
as a background service usingscripts/pkgsite.sh
. The script leverages browser-sync and nodemon to hot reload the Go module documentation on changes; many thanks to @mdaverde's Build your Golang package docs locally for paving the way.scripts/pkgsite.sh
adds automatic installation ofpkgsite
, as well as thebrowser-sync
andnodemon
npm packages for the local user. - view pkgsite: auxilliary task to open the VSCode-integrated "simple" browser
and pass it the local URL to open in order to show the module documentation
rendered by
pkgsite
. This requires a detour via a task input with ID "pkgsite".
make
: lists all targets.make coverage
: runs all tests with coverage and then updates the coverage badge inREADME.md
.make pkgsite
: installsx/pkgsite
, as well as thebrowser-sync
andnodemon
npm packages first, if not already done so. Then runs thepkgsite
and hot reloads it whenever the documentation changes.make report
: installs@gojp/goreportcard
if not yet done so and then runs it on the code base.make test
: runs all tests (including dynamic plugins).
plugger
is Copyright 2019-2022 Harald Albrecht, and licensed under the Apache
License, Version 2.0.