Golang events: EventEmitter / dispatcher for plugin architecture

余生长醉 提交于 2019-11-30 05:05:51
Not_a_Golfer

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!