douzhuangxuan3268 2015-01-17 16:55
浏览 278
已采纳

Golang事件:用于插件架构的EventEmitter / dispatcher

In Node.js I was able to make a WordPress clone rather easily using the EventEmitter to replicate and build a hooks-system into the CMS core, which plugins could then attach to.

I now need this same level of extensibility and core isolation for my CMS written in and ported to Go. Basically I have the core finished now, but in order to make it truly flexible I have to be able to insert events (hooks) and to have plugins attach to these hooks with additional functionality.

I don't care about recompiling (dynamic / static linking), as long as you don't have to modify the core to load plugins - the CMS core should never be modified. (like WP, Drupal etc.)

I noticed there's a few rather unknown projects, trying to implement events in Go looking somewhat similar to EventEmitter in Node.js:

https://github.com/CHH/eventemitter

https://github.com/chuckpreslar/emission

Since those 2 projects above haven't gained much popularity and attention somehow I feel this way of thinking about events might now be how we should do it in Go? Does this mean Go is maybe not geared to this task? To make truly extendable applications through plugins?

Doesn't seem like Go has events built into its core, and RPC doesn't seem like a valid solution for integrating plugins into your core application as were they built in natively, and as if they were part of the main application itself.

What's the best way for seamless plugin integration into your core app, for unlimited extension points (in core) without manipulating core every time you need to hook up a new plugin?

  • 写回答

1条回答 默认 最新

  • drzip28288 2015-01-17 19:02
    关注

    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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 使用ue5插件narrative时如何切换关卡也保存叙事任务记录
  • ¥20 软件测试决策法疑问求解答
  • ¥15 win11 23H2删除推荐的项目,支持注册表等
  • ¥15 matlab 用yalmip搭建模型,cplex求解,线性化处理的方法
  • ¥15 qt6.6.3 基于百度云的语音识别 不会改
  • ¥15 关于#目标检测#的问题:大概就是类似后台自动检测某下架商品的库存,在他监测到该商品上架并且可以购买的瞬间点击立即购买下单
  • ¥15 神经网络怎么把隐含层变量融合到损失函数中?
  • ¥15 lingo18勾选global solver求解使用的算法
  • ¥15 全部备份安卓app数据包括密码,可以复制到另一手机上运行
  • ¥20 测距传感器数据手册i2c