In general, in Go, if you need events you probably need to use channels, but if you need plugins, the way to go is
interfaces. Here's a bit lengthy example of a simple plugin architecture that minimizes the code that needs to be
written in the app's main file to add plugins (this can be automated but not dnyamic, see below).
I hope it's in the direction you're looking for.
1. The Plugin Interfaces
So okay, let's say we have two plugins, Fooer and Doer. We first define their interfaces:
// All DoerPlugins can do something when you call that method
type DoerPlugin interface {
DoSomething()
}
// All FooerPlugins can Foo() when you want them too
type FooerPlugin interface {
Foo()
}
2. The Plugin Registry
Now, our core app has a plugin registry. I'm doing something quicky and dirty here, just to get the idea across:
package plugin_registry
// These are are registered fooers
var Fooers = []FooerPlugin{}
// Thes are our registered doers
var Doers = []DoerPlugin{}
Now we expose methods to add plugins to the registry. The simple way is to add one per type, but you could
go with more complex reflection stuff and have one function. But usually in Go, try to keep things simple :)
package plugin_registry
// Register a FooerPlugin
func RegisterFooer(f FooerPlugin) {
Fooers = append(Fooers, f)
}
// Register a DoerPlugin
func RegisterDoer(d DoerPlugin) {
Doers = append(Doers, d)
}
3. Implementing and registering a plugin
Now, suppose this is your plugin module. We create a plugin that is a doer, and in our package's
init()
method we register it. init() happens once on program started for every imported package.
package myplugin
import (
"github.com/myframework/plugin_registry"
)
type MyPlugin struct {
//whatever
}
func (m *MyPlugin)DoSomething() {
fmt.Println("Doing something!")
}
Again, here is the "init magic" that registers the package automatically
func init() {
my := &MyPlugin{}
plugin_registry.RegisterDoer(my)
}
4. Importing the plugins registers them automatically
And now, the only thing we'll need to change is what we import into our main package. Since
Go doesn't have dynamic imports or linking, this is the only thing you'll need to write.
It's pretty trivial to create a go generate
script that will generate a main file
by looking into the file tree or a config file and finding all the plugins you need to import.
It's not dynamic but it can be automated. Because main imports the plugin for the side effect of the registration, the import uses the blank identifier to avoid unused import error.
package main
import (
"github.com/myframework/plugin_registry"
_ "github.com/d00dzzzzz/myplugin" //importing this will automaticall register the plugin
)
5. In the app's core
And now our core app doesn't need any code to change to be able to interact with plugins:
func main() {
for _, d := range plugin_registry.Doers {
d.DoSomething()
}
for _, f := range plugin_registry.Fooers {
f.Foo()
}
}
And that's about it. Keep in mind that the plugin registry should be a separate package
that both the app's core and the plugins can import, so you won't have circular imports.
Of course you can add event handlers to this mix, but as I've demonstrated, it's not needed.