2017. szeptember 26., kedd

Kiterjesztések írása Go nyelvi környezetben

Munkám során elkerülhetetlenné vált, hogy kicsit közelebbről is megismerjem a Go nyelv kiterjesztési képességeit. Konkrétan azt az aspektusát, amikor egy adott problémára több megoldás/kiterjesztés is létezik, és a programunknak futás időben kell a megfelelő implementációt felhasználnia. Tipikus példája ennek, amikor ugyanazon az interfészen keresztül különböző adatbazisokhoz tudunk kapcsolódni, tehát van az általanos dolgokat tartalmazó database/sql csomag, és a gyártó specifikus működést biztosító go-sql-driver/mysql. Az én esetemben azt kellett megoldani, hogy egy felhő alapú infrastruktúra automatizációs eszköz képes legyen szolgáltató specifikus ellenőrzéseket végezni.

Első lépésben hozzunk létre egy cloud nevű packaget és definiáljuk a közös interfészt, amit meg kell valósítaniuk az egyes kiterjesztéseknek. A példában egyetlen metódus szerepel, a CloudPlugin.Validate().

type CloudPlugin interface {
Validate()
}
Ezután deklaráljunk egy map típust, ami az elérhető kiterjesztéseket gyűjti, és egy metódust amin keresztül a kiterjesztések regisztrálni tudják magukat.

type Provider string
var providers map[Provider]CloudPlugin = make(map[Provider]CloudPlugin)
func Register(provider Provider, plugin CloudPlugin) {
providers[provider] = plugin
}
Valamint írjunk pár segédfüggvényt, hogy a kliens kód is hozzáférjen a regisztrált kiterjesztésekhez.

func Providers() (resp []Provider) {
for provider := range providers {
resp = append(resp, provider)
}
return
}
func GetProvider(provider Provider) CloudPlugin {
resp := providers[provider]
return resp
}
Miután a közös résszel végeztünk, ideje egy konkrét implementaciót megvalósítani.

const OPENSTACK = cloud.Provider("OPENSTACK")
type OpenstackPlugin struct {
}
func (p *OpenstackPlugin) Validate() {
println("openstack validator")
}
Amikor a Go runtime betolt egy packaget, akkor szépen sorban meghívja a fájlokban szereplő init() metódusokat, ezt használjuk fel arra, hogy a kiterjesztés beregisztrálja magát.

func init() {
cloud.Register(OPENSTACK, new(OpenstackPlugin))
}
Nincs más dolgunk mint felhasználni a kiterjesztést, amihez szinték kihasználjuk a Go nyelv egyik adottságát. Ahogyan metódus hívásnál alulvonással helyettesithetjük azt a változót, amivel nem szeretnénk foglalkozni, az importálásnál is lehetőségünk nyílik berántani egy packaget anélkül, hogy használnánk azt.

package main
import "github.com/mhmxs/go-plugins-tutorial/cloud"
import _ "github.com/mhmxs/go-plugins-tutorial/cloud/openstack"
func main() {
for _, provider := range cloud.Providers() {
cloud.GetProvider(provider).Validate()
}
}
view raw plugin-main.go hosted with ❤ by GitHub
Az eredmény pedig:
# go run main.go
openstack validator
Egy dologról szeretnék még szót ejteni. A cloud.Register() függvényt ha közelebbről megvizsgáljuk láthatjuk, hogy egyáltalán nem szál biztos, ami a példa program esetben nem okoz gondot. Viszont ha ismeretlen forrásból érkezhet regisztráció, akkor egy Mutexel garantálnunk kell a szálbiztos működést. Ezzel a témával a Go konkurencia kezelés gyorstalpaló cikkben foglalkoztam részletesebben.
A példaprogram teljes forráskódja a hiányzó RedHat implementációval együtt megtalálható GitHubon.