Documentation ¶
Overview ¶
Package plugger v3 implements a minimalist 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 using Group[T]().Symbols() 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.
Plugger v3 is safe for concurrent use (as opposed to v0/v2 that are not).
Usage ¶
Exposed plugin symbols are organized in PluginGroup objects, based on their particular type. The first step thus is to define a dedicated type for an exposed plugin symbol, such as a function or interface:
type PluginFn func(string) string
A good practice is to define the exported symbol types in a dedicated and otherwise empty package. This not only avoids import cycles but also ensures that always the same symbol type is used for looking up the corresponding PluginGroup object when working with symbols.
The PluginGroup for a specific type is retrieved by calling Group for the specific type:
group := plugger.Group[PluginFn]()
Calling Group multiple times for the same type always returns the same PluginGroup instance. There's no need for global variables referencing plugin group objects and using them should be avoided.
Next, plugins register their exposed symbols by retrieving the symbol's group first and then calling the plugger.PluginGroup.Register receiver on the group object.
func init() { plugger.Group[pluginFn]().Register(MyPluginFn) } func MyPluginFn() string { return "foo" }
Please note that plugger defaults to deriving the plugin name from the package name where plugger.PluginGroup.Register is called. The plugin name can also be explicitly specifyed by using WithPlugin in a registration.
Finally, when an application wants to invoke the registered symbols, it needs to grab the group object for the specific symbol type as before and then range over the group's exposed plugger.PluginGroup.Symbols.
import ( // don't forget to underline-import your (static) plugins! ) func main() { pluginFnGroup := plugger.Group[pluginFn]() for _, pluginFn := range pluginFnGroup.Symbols() { fmt.Println(pluginFn()) } }
Dynamically loading Plugins ¶
Specify the build tag/constraint “plugger_dynamic” and use github.com/thediveo/go-plugger/v3/dyn.Discover to discover and load plugin shared objects.
Upgrading from v0/v2 ¶
Plugger v3 simplifies the API while at the same time introducing type-safety for the exposed symbols. In v3, a given PluginGroup always contains only symbols of the same particular type, but never multiple different symbol types. In consequence, the overhead of naming exposed symbols in order to differentiate them could be removed; this v1/v2 feature wasn't really used anyway.
// 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))
In Unit Tests ¶
Sometimes, unit tests need a well-defined isolated plugin group configuration. For this, PluginGroup objects returned by Group() can now be backed up and restored using PluginGroup.Backup and PluginGroup.Restore. Additionally, PluginGroup.Clear resets a plugin group to its initial empty state.
Index ¶
- func WithPlacement(placement string) func(symbolSetter)
- func WithPlugin(name string) func(symbolSetter)
- type GroupStash
- type PluginGroup
- func (g *PluginGroup[T]) Backup() GroupStash[T]
- func (g *PluginGroup[T]) Clear()
- func (g *PluginGroup[T]) PluginSymbol(name string) T
- func (g *PluginGroup[T]) Plugins() []string
- func (g *PluginGroup[T]) PluginsSymbols() []Symbol[T]
- func (g *PluginGroup[T]) Register(symbol T, opts ...RegisterOption)
- func (g *PluginGroup[T]) Restore(s GroupStash[T])
- func (g *PluginGroup[T]) String() string
- func (g *PluginGroup[T]) Symbols() []T
- type RegisterOption
- type Symbol
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func WithPlacement ¶
func WithPlacement(placement string) func(symbolSetter)
WithPlacement registers an exposed symbol with the given (plugin) placement hint in plugger.PluginGroup.Register.
func WithPlugin ¶
func WithPlugin(name string) func(symbolSetter)
WithPlugin registers an exposed symbol with the given plugin name in plugger.PluginGroup.Register.
Types ¶
type GroupStash ¶ added in v3.1.0
type GroupStash[T any] struct { // contains filtered or unexported fields }
GroupStash is a “backup” of a PluginGroup. It can be used especially in unit tests where a PluginGroup needs to be modified to a particular known configuration for a test, and the group's original configuration restored after the test.
type PluginGroup ¶
type PluginGroup[T any] struct { // contains filtered or unexported fields }
PluginGroup represents the exposed plugin symbols for a particular symbol type, with the exposed symbols ordered by plugin name, or alternatively, by plugin placement.
func Group ¶
func Group[T any]() *PluginGroup[T]
Group returns the *PluginGroup object for the given exposed symbol type T. Calling Group multiple times for the same exposed symbol type T always returns the same PluginGroup object.
func (*PluginGroup[T]) Backup ¶ added in v3.1.0
func (g *PluginGroup[T]) Backup() GroupStash[T]
Save returns a copy of this plugin group's current plugin configuration, for later restoration using the Restore method.
func (*PluginGroup[T]) Clear ¶ added in v3.1.0
func (g *PluginGroup[T]) Clear()
Clears this plugin group's configuration (such as in unit tests).
func (*PluginGroup[T]) PluginSymbol ¶
func (g *PluginGroup[T]) PluginSymbol(name string) T
PluginSymbol returns the exposed symbol of the plugin identified by its name, or the zero symbol value if no such named plugin exists in this symbol group.
func (*PluginGroup[T]) Plugins ¶
func (g *PluginGroup[T]) Plugins() []string
Plugins returns the names of all plugins exposing symbols in this plugin group. The returned list is always ordered, based on the plugin names and placement hints.
func (*PluginGroup[T]) PluginsSymbols ¶
func (g *PluginGroup[T]) PluginsSymbols() []Symbol[T]
PluginsSymbols returns all exposed symbols together with the names of the plugins exposing them. This is always a clean and ordered copy of the Symbol objects.
func (*PluginGroup[T]) Register ¶
func (g *PluginGroup[T]) Register(symbol T, opts ...RegisterOption)
Register a plugin-exposed symbol, with optional additional registration information.
func (*PluginGroup[T]) Restore ¶ added in v3.1.0
func (g *PluginGroup[T]) Restore(s GroupStash[T])
Restore a plugin group's former plugin configuration from a backup previously created by the Backup method.
func (*PluginGroup[T]) String ¶
func (g *PluginGroup[T]) String() string
String renders a textual representation of a particular Group, showing the managed symbol type as well as the plugin-exposed symbols registered in this group.
func (*PluginGroup[T]) Symbols ¶
func (g *PluginGroup[T]) Symbols() []T
Symbols returns all symbols (functions or interfaces) exposed by the plugins in this Group. This is always a clean and ordered copy of the list of exposed symbols.
type RegisterOption ¶
type RegisterOption func(symbolSetter)
RegisterOption allows optional registration information to be passed to the Register method of plugin groups.
type Symbol ¶
type Symbol[T any] struct { S T // exposed function or interface symbol. Plugin string // name of plugin exposing the symbol S. Placement string // optional placement hint, or "". }
Symbol is a function or interface exposed by a (named) plugin. The interface must not be a constraint interface used to express type constraints.
The placement hint indicates where in an ordered list of the plugin symbols this plugin should be placed:
- "<": place at the beginning;
- ">": place at the end;
- "<foo": place before the plugin named "foo", if there is no such plugin named "foo", then the placement gets ignored;
- ">foo": place after the plugin named "foo", if there is no such plugin named "foo", then the placement gets ignored.
func (Symbol[T]) Validate ¶
func (s Symbol[T]) Validate()
Validate an exported plugin symbol and panic if the symbol is anything other than a function or interface.
While Go 1 has gained type constraints (in form of constraint interfaces) for use with Generics, there currently is no way to express constraints that forbid certain types, instead of allowing only a specific set. Thus, we need to validate at runtime that the symbol's type T actually is either a function type or an interface type. However, we cannot simply query the type of the symbol as this in the case of T being an interface would return the implementing value's T*. We thus need to construct a dummy composite type containing T that reflect accepts and then get that contained T's type via reflect. This then will be the correct interface T (instead of the underlying implementing value's T*). The Go compiler already ensured that the value satisfies the interface type T.
Directories ¶
Path | Synopsis |
---|---|
Package dyn discovers and loads .so Go plugins from the filesystem, so these plugins then can register themselves with the plugger plugin mechanism.
|
Package dyn discovers and loads .so Go plugins from the filesystem, so these plugins then can register themselves with the plugger plugin mechanism. |
example
|
|
barplug
Package barplug is an example plugin registering its exposed DoIt function symbol.
|
Package barplug is an example plugin registering its exposed DoIt function symbol. |
dynplug
Package dynplug is an example plugin registering its exposed DoIt function symbol; it intended to be loaded dynamically.
|
Package dynplug is an example plugin registering its exposed DoIt function symbol; it intended to be loaded dynamically. |
fooplug
Package fooplug is an example plugin registering its exposed DoIt function symbol.
|
Package fooplug is an example plugin registering its exposed DoIt function symbol. |
plugin
Package plugin defines the exposed example plugin API.
|
Package plugin defines the exposed example plugin API. |