Skip to content

rcoelho/go-plugins

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

Let users extend your Go programs using JavaScript and other dynamic runtimes

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 89.3%
  • JavaScript 9.9%
  • CoffeeScript 0.8%