Skriva plugins i Go

Go kunde inte ladda kod dynamiskt före Go 1.8. Jag är en stor förespråkare för plugin-baserade system, som i många fall kräver laddning av plugins dynamiskt. Jag övervägde ens när jag skrev ett pluginpaket baserat på C-integration.

Jag är väldigt exalterad att Go-designersna lagt till denna förmåga till språket. I den här handledningen lär du dig att plugins är så viktiga, vilka plattformar som stöds och hur man skapar, bygger, laddar och använder plugins i dina program.   

Skälen till Go Plugins

Go plugins kan användas för många ändamål. De låter dig bryta ner ditt system till en generisk motor som är lätt att motivera och testa, och många plugins följer ett strikt gränssnitt med väldefinierade ansvarsområden. Plugins kan utvecklas oberoende av huvudprogrammet som använder dem. 

Programmet kan använda olika kombinationer av plugins och även flera versioner av samma plugin samtidigt. De skarpa gränserna mellan huvudprogrammet och plugins främjar den bästa lösningen för lös koppling och separation av problem.

Paketet "plugin"

Det nya paketet "plugin" som introduceras i Go 1.8 har ett mycket smalt omfång och gränssnitt. Det ger den Öppna() funktion för att ladda ett delat bibliotek, vilket returnerar ett pluginobjekt. Plugin-objektet har a Slå upp() funktion som returnerar en symbol (tom gränssnitt ) hatt kan skrivas typ till en funktion eller variabel som exponeras av plugin. Det är allt.

Plattformsupport

Plug-paketet stöds endast på Linux just nu. Men det finns sätt som du ser att spela med plugins på vilket operativsystem som helst.

Förbereda en dockerbaserad miljö

Om du utvecklar på en Linux-låda behöver du bara installera Go 1.8 och du är bra att gå. Men om du är på Windows eller MacOS behöver du en Linux VM eller Docker-behållare. För att kunna använda det måste du först installera Docker.

När du har installerat Docker öppnar du ett konsolfönster och skriver: docker kör -it -v ~ / go: / go golang: 1,8-wheezy bash

Detta kommando kartlägger min lokala $ GOPATH~ / Go till /gå inuti behållaren. Det låter mig redigera koden med mina favoritverktyg på värden och få den tillgänglig i behållaren för att bygga och springa i Linux-miljön.

För mer information om Docker, kolla in min "Docker From the Ground Up" -serien här på Envato Tuts +:

  • Docker From the Ground Up: Förstå bilder
  • Docker From the Ground Up: Bygga bilder
  • Dockare från marken: Arbeta med behållare, del 1
  • Dockare från marken: Arbeta med behållare, del 2

Skapa ett Go-plugin

En Go-plugin ser ut som ett vanligt paket, och du kan även använda det som ett vanligt paket. Det blir bara ett plugin när du bygger det som ett plugin. Här är några plugins som implementerar a Sortera()funktion som sorterar ett segment av heltal. 

QuickSort Plugin

Det första pluginet implementerar en naiv QuickSort-algoritm. Implementeringen fungerar på skivor med unika element eller med dubbletter. Returvärdet är en pekare till ett segment av heltal. Det här är användbart för sorteringsfunktioner som sorterar deras element på plats eftersom det tillåter att återvända utan att kopiera. 

I det här fallet skapar jag faktiskt flera mellanliggande skivor, så effekten är oftast bortkastad. Jag offrar prestanda för läsbarhet här eftersom målet är att demonstrera plugins och inte implementera en super effektiv algoritm. Logiken går enligt följande:

  • Om det finns nollpunkter eller ett objekt, returnera originalskivan (redan sorterad).
  • Välj ett slumpmässigt element som en pinne.
  • Lägg till alla element som är mindre än pinnen till Nedan skiva.
  • Lägg till alla element som är större än pinnen till ovan skiva. 
  • Lägg till alla element som är lika med pinnen till mitten skiva.

Vid denna tidpunkt mitten skivan är sorterad eftersom alla dess element är lika (om det fanns dubbletter av pinnen, kommer det att finnas flera element här). Nu kommer den rekursiva delen. Det sorterar Nedan och ovan skivor genom att ringa Sortera() igen. När dessa samtal kommer tillbaka kommer alla skivor att sorteras. Sedan, helt enkelt lägga dem resulterar i en hel del av den ursprungliga skivan av objekt.

paket huvud import "matte / rand" func Sortera (poster [] int) * [] int om len (poster) < 2  return &items  peg := items[rand.Intn(len(items))] below := make([]int, 0, len(items)) above := make([]int, 0, len(items)) middle := make([]int, 0, len(items)) for _, item := range items  switch  case item < peg: below = append(below, item) case item == peg: middle = append(middle, item) case item > peg: över = lägg till (ovanför) nedan = * sortera (nedan) ovan = * sortera (ovan) sorterade: = lägg till (lägg till (nedanför, mellersta ...) ovanför ...) returnera och sortera

BubbleSort Plugin

Det andra pluginet implementerar BubbleSort-algoritmen på ett naivt sätt. BubbleSort anses ofta vara långsam, men för ett fåtal element och med lite mindre optimering slår det ofta mer sofistikerade algoritmer som QuickSort. 

Det är faktiskt vanligt att använda en hybrid sortalgoritm som börjar med QuickSort, och när rekursionen kommer till tillräckligt små arrays växlar algoritmen till BubbleSort. Bubbelsort plugin implementerar a Sortera() funktion med samma signatur som snabb sorteringsalgoritmen. Logiken går enligt följande:

  • Om det finns nollpunkter eller ett objekt, returnera originalskivan (redan sorterad).
  • Iterera över alla element.
  • I varje iteration, iterera över resten av föremålen.
  • Byt aktuellt objekt med något större objekt.
  • I slutet av varje iteration kommer nuvarande objekt att vara på rätt plats.
paket huvud func Sortera (poster [] int) * [] int om len (poster) < 2  return &items  tmp := 0 for i := 0; i < len(items); i++  for j := 0; j < len(items)-1; j++  if items[j] > objekt [j + 1] tmp = objekt [j] objekt [j] = objekt [j + 1] objekt [j + 1] = tmp retur och objekt 

Bygga plugin

Nu har vi två plugins som vi behöver bygga för att skapa ett delbart bibliotek som kan laddas dynamiskt av vårt huvudprogram. Kommandot att bygga är: gå bygg -buildmode = plugin

Eftersom vi har flera plugins placerade jag var och en i en separat katalog under en delad "plugins" -katalog. Här är kataloglayouten för plugin-katalogen. I varje plugin-underkatalog finns källfilen "_plugin.go "och ett litet skalskript" build.sh "för att bygga plugin. De slutliga .so-filerna går in i förlagets" plugins "-katalog:

$ plugins plugins ├── bubble_sort │ ├── bubble_sort_plugin.go │ └── build.sh ├── bubble_sort_plugin.so ├── quick_sort │ ├── build.sh │ └── quick_sort_plugin.go └── quick_sort_plugin .så

Anledningen till att * .so-filerna går in i plugin-katalogen är att de lätt kan upptäckas av huvudprogrammet, som du ser senare. Den faktiska byggkommandot i varje "build.sh" -skript specificerar att utdatafilen ska gå in i moderkatalogen. Till exempel är det för bubbla-sortimentet:

gå bygg -buildmode = plugin -o ... /bubble_sort_plugin.so

Läser in plugin

Att ladda pluginet kräver kunskap om var man ska placera målpluggarna (de *. Så delade biblioteken). Detta kan göras på olika sätt:

  • passande kommandoradsargument
  • ställer in en miljövariabel
  • använder en välkänd katalog
  • använder en konfigurationsfil

En annan oro är att huvudprogrammet känner till plugin namnen eller om det upptäcker dynamiskt alla plugins i en viss katalog. I det följande exemplet förväntar programmet att det kommer att finnas en underkatalog som heter "plugins" under den aktuella arbetsmappen, och det laddar alla plugins som den finner.

Samtalet till filepath.Glob ( "plugins / *. so") funktionen returnerar alla filer med ".so" -tillägget i plugin-underkatalogen och plugin.Open (filnamn) laddar in plugin. Om något går fel, panikprogrammet.

paketet huvudimport ("fmt" "plugin" "path / filepath") func main () all_plugins, err: = filepath.Glob ("plugins / * .so") om err! = nil panic (err) för _, filnamn: = intervall (all_plugins) fmt.Println (filnamn) p, err: = plugin.Open (filnamn) om err! = nil panic (err) 

Använda pluginprogrammet i ett program

Att hitta och ladda plugin är bara hälften av slaget. Plugin-objektet tillhandahåller Slå upp() Metod som ger ett symbolnamn returnerar ett gränssnitt. Du måste skriva hävdar det gränssnittet i ett konkret objekt (t.ex. en funktion som Sortera()). Det finns inget sätt att upptäcka vilka symboler som finns tillgängliga. Du behöver bara veta deras namn och deras typ, så du kan skriva påstå korrekt. 

När symbolen är en funktion kan du anropa den som någon annan funktion efter en framgångsrik typkrav. Följande exempelprogram visar alla dessa begrepp. Den laddar dynamiskt alla tillgängliga plugins utan att veta vilka plugins det finns förutom att de finns i underkatalogen "plugins". Det följer genom att slå upp "Sortera" -symbolen i varje plugin och skriv in det till en funktion med signaturen func ([] int) * [] int. Sedan, för varje plugin, det påstår sorteringsfunktionen med en skiva heltal och skriver resultatet.

paketet huvudimport ("fmt" "plugin" "path / filepath") func main () numbers: = [] int 5, 2, 7, 6, 1, 3, 4, 8 // Pluggarna * .so-filer) måste vara i en "plugins" -katalog all_plugins, err: = filepath.Glob ("plugins / * .so") om err! = nil panic (err) för _, filnamn: = (all_plugins) p, err: = plugin.Open (filnamn) om err! = nil panic (err) symbol, err: = p.Lookup ("Sortera") om err! = nil panic (err) sorteraFun, ok = symbol. (func ([] int) * [] int) om! ok panik ("Plugin har nej" Sortera ([] int) [] int " fmt.Println (filnamn, sorterad) Utmatning: plugins / bubble_sort_plugin.so & [1 2 3 4 5 6 7 8] plugins / quick_sort_plugin.so & [1 2 3 4 5 6 7 8] 

Slutsats

Paketet "plugin" ger en bra grund för att skriva sofistikerade Go-program som dynamiskt kan ladda pluggar efter behov. Programmeringsgränssnittet är mycket enkelt och kräver detaljerad kunskap om det använda programmet på plugingränssnittet. 

Det är möjligt att bygga ett mer avancerat och användarvänligt plugin-ramverk ovanpå "plugin" -paketet. Förhoppningsvis kommer det att skickas till alla plattformar snart. Om du installerar dina system på Linux, överväg att använda plugins för att göra dina program mer flexibla och utökbara.