go-plugins
early and experimental, but already badass
Let users extend your Go applications with JavaScript or any other runtime you implement.
Thanks to Otto, Go reflection, and inspired by Trac component architecture.
Using go-plugins
Extension Points
First, you define "extension points" for your application. An extension point is an API or a set of hooks that a plugin can implement to extend some aspect of your application. Defining an extension point involves writing an interface (though not a real interface) that plugins can implement, and then also an extension point singleton stub. Here is a simple observer pattern extension point:
type ProgramObserver struct {
ProgramStarted func()
ProgramEnded func()
}
var ProgramObserverExt struct {
Plugin func(string) ProgramObserver
Plugins func() []ProgramObserver
}
Then you register your extension point for it to be active:
plugins.ExtensionPoint(&ProgramObserverExt)
Now use the extension point in your program from the singleton, which has been fully implemented by declaring it with plugins.ExtensionPoint()
. .Plugins()
gets you all plugins implementing that extension point interface, whereas .Plugin(name)
lets you get a specific plugin by name. More often you use the former; the latter is used when you are using plugins to provide configurable backends. But here's .Plugins()
in our app:
for _, observer := range ProgramObserverExt.Plugins() {
observer.ProgramStarted()
}
fmt.Println("Hello World")
for _, observer := range ProgramObserverExt.Plugins() {
observer.ProgramEnded()
}
Without plugins loaded, when we run the output, it's pretty boring:
Hello World
Runtimes
Plugins run in runtimes, which define a scripting environment. Out of the box, we have ottojs
, which is a JavaScript runtime based on Otto, a pure Go JavaScript interpreter. You can define your own runtimes to hook up and script in Python, Lua, or anything else. You can even support multiple runtimes at once! Just register them at the beginning of your program:
plugins.RegisterRuntime(ottojs.GetRuntime())
Writing runtimes is relatively easy. You just implemenet the plugins.Runtime
interface. Here's a runtime implementation for Lua that took about 30 minutes to write.
Plugins
We're about to write a plugin! We'll call it happy.js
and put it in a plugins
directory. This is the default place to look when you load with plugins.LoadFromPath()
, which you can override with the PLUGIN_PATH
environment variable. There are plenty of other ways to load plugins, but this is the easiest:
plugins.LoadFromPath()
And now some JavaScript. Our plugins/happy.js
file:
implements("ProgramObserver")
function ProgramStarted() {
console.log("Yay! It's starting!")
}
function ProgramEnded() {
console.log("Yay! It's over!")
}
A plugin can implement any number of extension point interfaces by calling implements()
multiple times. With this when we run our program:
Yay! It's starting!
Hello World
Yay! It's over!
Change the text in the plugin and run again. No need to recompile your Go. Add another plugin. Remove all plugins. It just works. You can see the full source for this example or look at all the examples.
Globals
You can set shared global values for all plugins. This is useful for exposing an API to plugins. You can expose any value the runtime supports, however they aren't kept synchronized. If a plugin changes a value, that change is not reflected in other plugins, so they should be treated as immutable.
plugins.SetGlobals(map[string]interface{}{
"Plugin.Hello": "Hello world",
"Plugin.Print": func(text string) {
fmt.Println(text)
},
})
This would expose a string and a function to plugins under a global Plugin
object. You can use any object namespace and the parent objects will be created by the runtime. Using these in the plugin might look like this:
function UseGlobals() {
Plugin.Print(Plugin.Hello)
}
Static Plugins
Now that you have all these extension points defined in your code, maybe you want to use them yourself from Go. Or maybe you're writing a Go library and you want to expose extension points. Static plugins work just like regular plugins, except they're defined in Go.
type MyStaticPlugin struct {}
func (p MyStaticPlugin) ProgramStarted() {
fmt.Println("Static plugin: start")
}
func (p MyStaticPlugin) ProgramEnded() {
fmt.Println("Static plugin: end")
}
The only difference is that you register them manually, specifying the interfaces they implement:
plugins.StaticPlugin(&MyStaticPlugin{}, []string{
"ProgramObserver",
})
Static and regular plugins can live side-by-side, but if you just wanted to use static plugins, simply skip registering any runtimes.
Static plugins can also access any globals that have been set. You just use the plugins.GetGlobal(name)
function. However, it returns an interface{}
value, so you need to use type assertion. It might be more convenient to not use globals if you only have static plugins. Or if you do plan to support dynamic plugins, set your globals with references to objects that can be accessed directly by static plugins.
License
BSD