Abordagem idiomática para um sistema baseado em plugin Go - 1 respostas
#1 resposta do Christian Grabowski (2 votos)
Eu tenho um projeto Go que gostaria de abrir o código-fonte, mas há certos elementos que não são adequados para o OSS, por exemplo, lógica específica da empresa, etc.
Eu concebi a seguinte abordagem:
interface s estão definidos no repositório principal.
Os plug-ins podem então ser repositórios autônomos, cujo type s implementa o interface s definido no núcleo. Isso permite que os plug-ins sejam armazenados em módulos completamente separados e, portanto, tenham seus próprios trabalhos de CI, etc.
Os plug-ins são compilados no binário final por meio de links simbólicos.
Isso resultaria em uma estrutura de diretórios algo como o seguinte:
|- $GOPATH
|- jabclab
|- core-system
|- plugins <-----|
|- xxx               |
|- plugin-a ------>| ln -s
|- yyy               |
|- plugin-b ------>|
Com um exemplo de fluxo de trabalho de:
$ 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
$ ln -s ./xxx/plugin-a/*.go ./jabclab/core-system/plugins
$ ln -s ./yyy/plugin-b/*.go ./jabclab/core-system/plugins
$ cd jabclab/core-system
O único problema sobre o qual não tenho certeza é como tornar os tipos definidos em plug-ins disponíveis no tempo de execução no core. Prefiro não usar reflect , mas não consigo pensar em uma maneira melhor no momento. Se eu estivesse fazendo o código em um repo, usaria algo como:
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{} }
Além da pergunta acima, essa abordagem geral seria considerada idiomática?
por jabclab 29.02.2016 в 21:05
Este é um dos meus problemas favoritos no Go. Eu tenho um projeto de código aberto que tem que lidar com isso também ( link ), ele tem DB e Runtime conectáveis (Docker, k8s , Mesos, etc) clientes. Antes do pacote de plugins que está no branch master do Go (então ele deve estar chegando a uma versão estável em breve) eu apenas compilei todos os plugins no binário e a configuração permitida decidiu qual usar.
A partir do pacote de plugins, o link , você pode usar links dinâmicos para plug-ins, de modo semelhante a C's dlopen() em seu carregamento, e o comportamento do pacote de plug-ins do go está bem descrito na documentação.
Além disso, recomendo dar uma olhada em como a Hashicorp resolve isso fazendo RPC em um soquete unix local. link
O benefício adicional de executar um plugin como um processo separado, como o modelo da Hashicorp, é que você obtém grande estabilidade no caso de o plug-in falhar, mas o processo principal é capaz de lidar com essa falha.
Eu também devo mencionar que o Docker faz seus plugins no Go da mesma forma, exceto que o Docker usa HTTP em vez de RPC. Além disso, um engenheiro do Docker postou a inclusão de um intérprete de JavaScript para lógica dinâmica no link anterior.
O problema que eu queria ressaltar com o padrão do pacote sql mencionado nos comentários é que, na verdade, isso não é uma arquitetura de plug-in, você ainda está limitado a tudo o que está em suas importações, portanto você pode ter vários main.go mas isso não é um plugin, o ponto de um plugin é tal que o mesmo programa pode executar um pedaço de código ou outro. O que você tem com coisas como o pacote sql é a flexibilidade, onde um pacote separado determina qual driver de banco de dados usar. No entanto, você acaba modificando o código para alterar o driver que está usando.
Eu quero adicionar, com todos esses padrões de plugins, além de compilar no mesmo binário e usar a configuração para escolher, cada pode ter sua própria compilação, teste e implementação (isto é, sua própria CI / CD), mas não necessariamente.