Plugins in go (lang, não horse)

Eu gosto bastante da ideia de programas que potencialmente podem ser a junção e peças menores, unix pipes, serviços remotos e coisas do tipo e permitem escrever programas menores que se comunicam via uma interface definida. Nessa mesma linha, plugins podem ser bem úteis extendendo um programa sem que o programa principal saiba do que está acontecendo.

Em go existe o package plugin que permite escrever e carregar plugins dinamicamente. Basicamente o que precisa ser feito é definir o que o plugin precisa implementar (uma ou mais funções) e o programa principal carrega o plugin em tempo de execução e executa as funções implementadas pelo plugin.

O programa principal

A primeira coisa que precisamos é de um programa que seja capaz de carregar e executar plugins. Este será o programa principal que será extendidos pelos plugins. Para carregar o plugin se usa plugin.Open e plugin.Plugin.Lookup.

Então comecemos com um programa bem simples, sem suporte a plugins:

package main

import (
    "flag"
    "os"
)

func main() {
    action := flag.String("action", "faz", "")
    flag.CommandLine.Parse(os.Args[1:])

    s := "coisa"

    println(*action + " " + s)
}
$ ./programa
faz coisa
$./programa -action outra
outra coisa

Nada muito o que explicar aqui, né? Então agora vamos implementar o suporte a plugins.

A ideia agora é que a gente vai usar plugins baseados no parâmetro action que o usuário passar.

package main

import (
    "flag"
    "fmt"
    "os"
v    "plugin"
)

func main() {
    action := flag.String("action", "faz", "")
    flag.CommandLine.Parse(os.Args[1:])
    s := "coisa"

    var f plugin.Symbol
    var fn func(string)
    var ok bool

    // Aqui plugin.Open recebe o caminho do plugin.
    // A gente vai procurar por um plugin baseado
    // na action que o usuário passou
    fpath := fmt.Sprintf("./%s_plugin.so", *action)
    p, err := plugin.Open(fpath)

    if err != nil {
        // Olha esse goto maroto engolindo os bug tudo!
        goto PRINT
    }

    // Aqui a gente procura no plugin por um símbolo
    // com o nome de Action
    f, err = p.Lookup("Action")
    if err != nil {
        goto PRINT
    }

    // Aqui verifica se o símbolo é realmente do tipo
    // que a gente espera
    fn, ok = f.(func(string))
    if !ok {
        goto PRINT
    }

    fn(s)
    return

PRINT:
    println(*action + " " + s)

}

Por enquanto como não temos nenhum plugin nosso programa continua fazendo a mesma coisa:

$./programa
faz coisa
$./programa -action outra
outra coisa

O plugin

Para implementar o plugin a gente precisa só implementar uma função chamada Action que recebe uma string como parâmetro:

package main

func Action(s string) {
    println("Aqui o plugin fazendo outra " + s)
}

Agora o plugin precisa ser compilado com a flag -buildmode=plugin

go build -buildmode=plugin outra_plugin.go

E agora nosso programa  pode usar o plugin:
$./programa
faz coisa
$./programa -action outra
Aqui o plugin fazendo outra coisa

E é isso!