基于Go插件的系统的惯用法

时间:2016-02-29 20:05:49

标签: go

我有一个Go项目我想开源,但有些元素不适合OSS,例如:公司特定的逻辑等。

我构思了以下方法:

  • interface已在核心存储库中定义。
  • 然后,插件可以是独立的存储库,其type实现核心中定义的interface。这允许插件安装在完全独立的模块中,因此具有自己的CI作业等。
  • 通过符号链接将插件编译成最终的二进制文件。

这将导致目录结构类似于以下内容:

|- $GOPATH
  |- src
    |- github.com
      |- jabclab
        |- core-system
          |- plugins <-----|
      |- xxx               | 
        |- plugin-a ------>| ln -s
      |- yyy               |  
        |- plugin-b ------>|

使用以下示例工作流程:

$ go get git@github.com:jabclab/core-system.git
$ go get git@github.com:xxx/plugin-a.git
$ go get git@github.com:yyy/plugin-b.git
$ cd $GOPATH/src/github.com
$ ln -s ./xxx/plugin-a/*.go ./jabclab/core-system/plugins
$ ln -s ./yyy/plugin-b/*.go ./jabclab/core-system/plugins
$ cd jabclab/core-system
$ go build

我不确定的一个问题是如何使插件中定义的类型在运行时在核心中可用。我宁愿不使用reflect,但现在想不出更好的方法。如果我在一个回购中执行代码,我会使用类似的东西:

package plugins

type Plugin interface {
  Exec(chan<- string) error
}

var Registry map[string]Plugin

// plugin_a.go
func init() { Registry["plugin_a"] = PluginA{} }

// plugin_b.go
func init() { Registry["plugin_b"] = PluginB{} }

除了上述问题,这种整体方法是否会被认为是惯用的?

1 个答案:

答案 0 :(得分:2)

这是Go中我最喜欢的问题之一。我有一个开源项目,也必须处理这个(https://github.com/cpg1111/maestrod),它有可插入的DB和运行时(Docker,k8s,Mesos等)客户端。在Go的主分支中的插件包之前(所以它应该很快就会出现稳定版本)我只是将所有插件编译成二进制文件并允许配置决定使用哪个。

从插件包https://tip.golang.org/pkg/plugin/开始,您可以使用插件的动态链接,因此在加载时类似于C dlopen(),以及go插件的行为软件包在文档中有很好的概述。

此外,我建议看看Hashicorp如何通过在本地unix套接字上执行RPC来解决这个问题。 https://github.com/hashicorp/go-plugin

将插件作为一个独立的进程(如Hashicorp的模型)运行的额外好处是,如果插件失败但主进程能够处理该失败,您将获得极大的稳定性。

我还应该提到Docker在Go中的插件类似,除了Docker使用HTTP而不是RPC。此外,Docker工程师在过去http://crosbymichael.com/category/go.html发布了关于为动态逻辑嵌入Javascript解释器的信息。

我想用评论中提到的sql包的模式指出的问题是,这不是一个真正的插件架构,你仍然只限于你的任何内容导入,所以你可以拥有多个main.go,但这不是一个插件,插件的重点是同一个程序可以运行一段代码或另一段代码。 sql包之类的东西是灵活性,单独的包决定了要使用的DB驱动程序。尽管如此,您最终修改代码以更改正在使用的驱动程序。

我想添加所有这些插件模式,除了编译成相同的二进制文件并使用配置选择外,每个都可以拥有自己的构建,测试和部署(即他们自己的CI / CD)但不一定。