Skriver plugins i Go

Go kunne ikke laste kode dynamisk før Go 1.8. Jeg er en stor forutsetning for plugin-baserte systemer, som i mange tilfeller krever dynamisk innlasting av plugins. Jeg har til og med vurdert på et tidspunkt å skrive en plugin pakke basert på C integrasjon.

Jeg er veldig spent at Go-designerne la denne muligheten til språket. I denne opplæringen lærer du hvorfor plugins er så viktige, hvilke plattformer som støttes, og hvordan du lager, bygger, laster og bruker plugins i programmene dine.   

Begrunnelsen for Go Plugins

Go plugins kan brukes til mange formål. De lar deg slå systemet ned i en generisk motor som er lett å begrunnelse om og teste, og mange plugins følger et strenkt grensesnitt med veldefinerte ansvar. Plugins kan utvikles uavhengig av hovedprogrammet som bruker dem. 

Programmet kan bruke forskjellige kombinasjoner av plugins og til og med flere versjoner av samme plugin samtidig. De skarpe grensen mellom hovedprogrammet og pluginene fremmer den beste løsningen for løs kopling og adskillelse av bekymringer.

Pakken "Plugin"

Den nye "plugin" -pakken introdusert i Go 1.8 har et veldig smalt omfang og grensesnitt. Det gir Åpen() funksjon for å laste inn et delt bibliotek, som returnerer et Plugin-objekt. Plugin-objektet har en Se opp() funksjon som returnerer en symbol (tom grensesnitt ) -hue kan skrives til en funksjon eller variabel som er eksponert av plugin-modulen. Det er det.

Plattformstøtte

Pluggpakken støttes bare på Linux nå. Men det er måter, som du ser, å spille med plugins på hvilket som helst operativsystem.

Forbereder et dockerbasert miljø

Hvis du utvikler på en Linux-boks, trenger du bare å installere Go 1.8, og du er god til å gå. Men hvis du er på Windows eller MacOS, trenger du en Linux VM eller Docker container. For å bruke den må du først installere Docker.

Når du har installert Docker, åpner du et konsollvindu og skriver inn: docker løp -it -v ~ / go: / go golang: 1,8-wheezy bash

Denne kommandoen kartlegger min lokale $ GOPATH~ / Gå til /gå inne i beholderen. Det gjør at jeg kan redigere koden ved hjelp av mine favorittverktøy på verten og få den tilgjengelig inne i containeren for å bygge og kjøre i Linux-miljøet.

For mer informasjon om Docker, sjekk ut min "Docker From the Ground Up" -serien her på Envato Tuts +:

  • Docker fra bakken: forstå bilder
  • Docker fra bakken: Bygg bilder
  • Docker fra bakken: Arbeid med containere, del 1
  • Docker fra bakken: Arbeid med containere, del 2

Opprette en Go-plugin

En Go-plugin ser ut som en vanlig pakke, og du kan også bruke den som en vanlig pakke. Det blir bare et plugin når du bygger det som et plugin. Her er et par plugins som implementerer a Sortere()funksjon som sorterer et stykke heltall. 

QuickSort Plugin

Den første plugin implementerer en naiv QuickSort-algoritme. Implementeringen fungerer på skiver med unike elementer eller med duplikater. Returneringsverdien er en peker til et stykke heltall. Dette er nyttig for sorteringsfunksjoner som sorterer elementene på plass fordi det gjør det mulig å returnere uten å kopiere. 

I dette tilfellet lager jeg faktisk flere midlertidige skiver, så effekten er for det meste bortkastet. Jeg ofrer ytelse for lesbarhet her siden målet er å demonstrere plugins og ikke implementere en super effektiv algoritme. Logikken går som følger:

  • Hvis det er null elementer eller ett element, returner du den opprinnelige skiven (allerede sortert).
  • Velg et tilfeldig element som en pinne.
  • Legg til alle elementene som er mindre enn pinnen til under skjære.
  • Legg til alle elementene som er større enn pinnen til ovenfor skjære. 
  • Legg til alle elementene som er lik pinnen til midten skjære.

På dette punktet, den midten skive er sortert fordi alle elementene er like (hvis det var duplikater av pinnen, vil det være flere elementer her). Nå kommer den rekursive delen. Det sorterer under og ovenfor skiver ved å ringe Sortere() en gang til. Når disse anropene kommer tilbake, blir alle skivene sortert. Så, ved å legge dem enkelt, resulterer det i en fullstendig sortering av den originale delen av elementene.

pakke viktigste import "matte / rand" func Sorter (elementer [] int) * [] int hvis len (elementer) < 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: over = legg til (over, element) under = * Sorter (under) over = * Sorter (over) sortert: = legg til (legg til (under, midt ...), over ...) returnere og sortert

BubbleSort Plugin

Det andre pluginet implementerer BubbleSort-algoritmen på en naiv måte. BubbleSort anses ofte sakte, men for et lite antall elementer og med litt mindre optimalisering slår det ofte mer sofistikerte algoritmer som QuickSort. 

Det er faktisk vanlig å bruke en hybrid sorteringsalgoritme som starter med QuickSort, og når rekursjonen kommer til små nok arrays, bytter algoritmen til BubbleSort. Boble-typen plugin implementerer a Sortere() Fungerer med samme signatur som hurtig sorteringsalgoritmen. Logikken går som følger:

  • Hvis det er null elementer eller ett element, returner du den opprinnelige skiven (allerede sortert).
  • Iterere over alle elementene.
  • I hver iterasjon, iterate over resten av elementene.
  • Bytt gjeldende element med noe som er større.
  • På slutten av hver iterasjon vil gjeldende element være på sitt rette sted.
pakke main func Sorter (elementer [] int) * [] int hvis len (elementer) < 2  return &items  tmp := 0 for i := 0; i < len(items); i++  for j := 0; j < len(items)-1; j++  if items[j] > elementer [j + 1] tmp = elementer [j] elementer [j] = elementer [j + 1] elementer [j + 1] = tmp retur og elementer 

Bygg pluggen

Nå har vi to plugins vi må bygge for å opprette et delbart bibliotek som kan lastes dynamisk av vårt hovedprogram. Kommandoen å bygge er: gå bygge -buildmode = plugin

Siden vi har flere plugins, plasserte jeg hver i en egen katalog under en delt "plugins" -katalog. Her er katalogoppsettet til plugin-katalogen. I hver plugin undermappe er det kildefilen "_plugin.go "og et lite shell script" build.sh "for å bygge plugin. De endelige .so-filene går inn i den overordnede" plugins "-katalogen:

$ 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å

Grunnen til at * .so-filene går inn i plugin-katalogen er at de lett kan oppdages av hovedprogrammet, som du vil se senere. Den faktiske byggekommandoen i hvert "build.sh" -skript angir at utdatafilen skal gå inn i overordnet katalog. For eksempel, for boble sortering plugin er det:

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

Laster inn plugin

Laster pluginet krever kunnskap om hvor du skal finne målpluggene (* .so delte biblioteker). Dette kan gjøres på ulike måter:

  • passerende kommandolinjeparametere
  • Innstilling av en miljøvariabel
  • bruker en kjent katalog
  • bruker en konfigurasjonsfil

En annen bekymring er at hvis hovedprogrammet kjenner pluginnavnene eller om det oppdager dynamisk alle pluginene i en bestemt katalog. I det følgende eksemplet forventer programmet at det vil være en underkatalog kalt "plugins" under den nåværende arbeidskatalogen, og den laster alle plugins den finner.

Anropet til filepath.Glob ( "plugins / *. så") funksjonen returnerer alle filene med ".so" -utvidelsen i plugin-underkatalogen og plugin.Open (filnavn) laster inn plugin. Hvis noe går galt, blir programmet panikk.

pakke hovedinnføring ("fmt" "plugin" "path / filepath") func main () all_plugins, err: = filepath.Glob ("plugins / * .so") hvis err! = null panic (err) for _, filnavn: = rekkevidde (all_plugins) fmt.Println (filnavn) p, err: = plugin.Open (filnavn) hvis err! = null panic (err) 

Bruke pluginprogrammet i et program

Å finne og laste inn plugin er bare halvparten av kampen. Pluggobjektet gir Se opp() metode som gir et symbolnavn returnerer et grensesnitt. Du må skrive angi det grensesnittet i et konkret objekt (for eksempel en funksjon som Sortere()). Det er ingen måte å oppdage hvilke symboler som er tilgjengelige. Du må bare kjenne navnene deres og deres type, slik at du kan skrive påstå riktig. 

Når symbolet er en funksjon, kan du påkalle det som en hvilken som helst annen funksjon etter en vellykket typegjenkjenning. Følgende eksempelprogram viser alle disse konseptene. Den laster dynamisk alle tilgjengelige plugins uten å vite hvilke plugins det er, bortsett fra at de er i underkatalogen "plugins". Det følger ved å slå opp "Sorter" -symbolet i hvert plugin og skriv inn det til en funksjon med signaturen func ([] int) * [] int. Så, for hvert plugin, påkaller den sorteringsfunksjonen med et stykke heltall og skriver resultatet.

pakke hovedinnføring ("fmt" "plugin" "path / filepath") func main () tall: = [] int 5, 2, 7, 6, 1, 3, 4, 8 // Plugins * .so filer) må være i en 'plugins' underkatalog all_plugins, err: = filepath.Glob ("plugins / * .so") hvis err! = null panikk (err) for _, filnavn: = rekkevidde (all_plugins) p, err: = plugin.Open (filnavn) hvis err! = null panic (err) symbol, err: = p.Lookup ("Sorter") hvis err! = null panikk (feil) sortertFun, ok = symbol. (func ([] int) * [] int) hvis! ok panikk ("Plugin har nei" Sorter ([] int) [] int 'funksjon ") sortert: = fmt.Println (filnavn, sortert) Utgang: 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] 

Konklusjon

Pakken "plugin" gir et godt grunnlag for å skrive sofistikerte Go-programmer som dynamisk kan laste inn plugins etter behov. Programmeringsgrensesnittet er veldig enkelt og krever detaljert kunnskap om bruk av program på plugingrensesnittet. 

Det er mulig å bygge et mer avansert og brukervennlig plugin-rammeverk på toppen av "plugin" -pakken. Forhåpentligvis vil det bli sendt til alle plattformene snart. Hvis du distribuerer systemene dine på Linux, bør du vurdere å bruke plugins for å gjøre programmene mer fleksible og utvidbare.