我有一个Go项目我想开源,但有些元素不适合OSS,例如公司特定的逻辑等.
我构思了以下方法:
interfaces在核心存储库中定义.type实现interface在核心中定义的s.这允许插件容纳在完全独立的模块中,因此具有自己的CI作业等.这将导致目录结构如下所示:
|- $GOPATH
|- src
|- github.com
|- jabclab
|- core-system
|- plugins <-----|
|- xxx |
|- plugin-a ------>| ln -s
|- yyy |
|- plugin-b ------>|
Run Code Online (Sandbox Code Playgroud)
使用以下示例工作流程:
$ 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
Run Code Online (Sandbox Code Playgroud)
我不确定的一个问题是如何使插件中定义的类型在运行时在核心中可用.我宁愿不使用,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{} }
Run Code Online (Sandbox Code Playgroud)
除了上述问题,这种整体方法是否会被认为是惯用的?
这是 Go 中我最喜欢的问题之一。我有一个开源项目也必须处理这个问题(https://github.com/cpg1111/maestrod),它有可插入的数据库和运行时(Docker、k8s、Mesos 等)客户端。在 Go 主分支中的插件包之前(所以它应该很快就会发布稳定版本),我只是将所有插件编译到二进制文件中,并允许配置决定使用哪个。
从插件包https://tip.golang.org/pkg/plugin/开始,您可以使用插件的动态链接,因此dlopen()在加载方面与 C 类似,并且 go 插件包的行为在文档。
另外,我建议看看 Hashicorp 如何通过本地 unix 套接字执行 RPC 来解决这个问题。 https://github.com/hashicorp/go-plugin
像 Hashicorp 的模型一样,将插件作为单独的进程运行的额外好处是,如果插件发生故障,您可以获得很大的稳定性,但主进程能够处理该故障。
我还应该提到 Docker 在 Go 中的插件类似,只是 Docker 使用 HTTP 而不是 RPC。此外,一位 Docker 工程师过去曾发布过关于嵌入 Javascript 解释器以实现动态逻辑的文章http://crosbymichael.com/category/go.html。
我想指出的问题是,评论中提到的 sql 包的模式实际上并不是一个插件架构,您仍然受限于导入中的任何内容,因此您可以有多个 main.go,但这不是插件,插件的要点是同一个程序可以运行一段代码或另一段代码。sql 包之类的东西具有灵活性,其中单独的包确定要使用的数据库驱动程序。尽管如此,您最终还是要修改代码来更改您正在使用的驱动程序。
我想补充一点,对于所有这些插件模式,除了编译成相同的二进制文件并使用配置进行选择之外,每个插件模式都可以有自己的构建、测试和部署(即它们自己的 CI/CD),但不一定。