Plugins schrijven in Go

Go kon de code niet dynamisch laden vóór Go 1.8. Ik ben een groot voorstander van op plug-ins gebaseerde systemen, die in veel gevallen dynamische laadplug-ins vereisen. Ik heb zelfs op een bepaald moment overwogen om een ​​plug-inpakket te schrijven op basis van C-integratie.

Ik ben super enthousiast dat de ontwerpers van Go deze mogelijkheid hebben toegevoegd aan de taal. In deze zelfstudie leer je waarom plug-ins zo belangrijk zijn, welke platforms momenteel worden ondersteund en hoe je plug-ins in je programma's kunt maken, bouwen, laden en gebruiken..   

De reden voor Go-plugins

Go-plug-ins kunnen voor veel doeleinden worden gebruikt. Ze laten u uw systeem opsplitsen in een generieke engine die gemakkelijk te redeneren en te testen is, en veel plug-ins houden zich aan een strikte interface met duidelijk omschreven verantwoordelijkheden. Plug-ins kunnen onafhankelijk van het hoofdprogramma dat ze gebruikt, worden ontwikkeld. 

Het programma kan verschillende combinaties van plug-ins en zelfs meerdere versies van dezelfde plug-in tegelijkertijd gebruiken. De scherpe grenzen tussen het hoofdprogramma en plug-ins bevorderen de beste praktijk van losse koppeling en scheiding van punten van zorg.

Het pakket "plug-in"

Het nieuwe "plug-in" -pakket dat is geïntroduceerd in Go 1.8 heeft een zeer beperkte reikwijdte en interface. Het biedt de Open() functie om een ​​gedeelde bibliotheek te laden, die een Plugin-object retourneert. Het Plugin-object heeft een Opzoeken() functie die een Symbool retourneert (lege interface ) hoed kan van het type worden bevestigd aan een functie of variabele die door de plugin wordt belicht. Dat is het.

Platformondersteuning

Het plugin-pakket wordt momenteel alleen ondersteund door Linux. Maar er zijn manieren, zoals je zult zien, om met plug-ins op elk besturingssysteem te spelen.

Een op dockers gebaseerde omgeving voorbereiden

Als je aan het ontwikkelen bent op een Linux-box, dan moet je gewoon Go 1.8 installeren en je bent klaar om te gaan. Maar als u Windows of macOS gebruikt, hebt u een Linux VM- of Docker-container nodig. Om het te gebruiken, moet u eerst Docker installeren.

Zodra je Docker hebt geïnstalleerd, open je een consolevenster en typ je: havenarbeiderloop -it -v ~ / go: / go golang: 1.8-wheezy bash

Met deze opdracht wordt mijn lokale kaart toegewezen $ GOPATH op ~ / Go naar /Gaan in de container. Hiermee kan ik de code bewerken met behulp van mijn favoriete hulpprogramma's op de host en deze binnen de container beschikbaar maken voor opbouwen en uitvoeren in de Linux-omgeving.

Voor meer informatie over Docker, bekijk mijn serie "Docker from the Ground Up" hier op Envato Tuts +:

  • Docker vanuit de grond omhoog: afbeeldingen begrijpen
  • Docker vanuit de grond omhoog: afbeeldingen bouwen
  • Docker vanuit de grond omhoog: werken met containers, deel 1
  • Docker vanuit de grond omhoog: werken met containers, deel 2

Een Go-plug-in maken

Een Go-plug-in ziet eruit als een standaardpakket en u kunt het ook als een standaardpakket gebruiken. Het wordt alleen een plug-in als je het als plug-in bouwt. Hier zijn een paar plug-ins die een a implementeren Soort()functie die een segment van gehele getallen sorteert. 

QuickSort-plug-in

De eerste plug-in implementeert een naïef QuickSort-algoritme. De implementatie werkt op slices met unieke elementen of met duplicaten. De retourwaarde is een verwijzing naar een segment van gehele getallen. Dit is handig voor sorteerfuncties die hun elementen op hun plaats sorteren, omdat het terugkeert zonder te kopiëren. 

In dit geval maak ik eigenlijk meerdere tussentijdse slices, dus het effect is grotendeels verspild. Ik offer hier prestaties voor leesbaarheid omdat het doel is om plug-ins te demonstreren en geen superefficiënt algoritme te implementeren. De logica gaat als volgt:

  • Als er nul items of één item zijn, retourneert u de originele slice (al gesorteerd).
  • Kies een willekeurig element als een peg.
  • Voeg alle elementen toe die minder zijn dan de koppeling naar de beneden plak.
  • Voeg alle elementen toe die groter zijn dan de koppeling naar de bovenstaande plak. 
  • Voeg alle elementen toe die gelijk zijn aan de koppeling aan de midden- plak.

Op dit punt, de midden- slice wordt gesorteerd omdat alle elementen gelijk zijn (als er duplicaten van de peg zouden zijn, zullen er hier meerdere elementen zijn). Nu komt het recursieve deel. Het sorteert de beneden en bovenstaande plakjes door te roepen Soort() nog een keer. Wanneer die oproepen terugkomen, worden alle segmenten gesorteerd. Als u ze gewoon toevoegt, resulteert dit in een volledig soort origineel stuk.

pakket main import "math / rand" func Sort (items [] int) * [] int if len (items) < 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: boven = toevoegen (hierboven, item) onder = * Sorteren (hieronder) hierboven = * Sorteren (hierboven) gesorteerd: = toevoegen (toevoegen (hieronder, midden ...), boven ...) terugzenden & sorteren

BubbleSort-plug-in

De tweede plug-in implementeert het BubbleSort-algoritme op een naïeve manier. BubbleSort wordt vaak als traag beschouwd, maar voor een klein aantal elementen en met wat kleine optimalisatie is het vaak beter gesofisticeerde algoritmen zoals QuickSort. 

Het is gebruikelijk om een ​​hybride sorteeralgoritme te gebruiken dat begint met QuickSort, en wanneer de recursie te klein wordt voor arrays, schakelt het algoritme over op BubbleSort. De plug-in voor bellen sorteren implementeert a Soort() functie met dezelfde handtekening als het snel sorteeralgoritme. De logica gaat als volgt:

  • Als er nul items of één item zijn, retourneert u de originele slice (al gesorteerd).
  • Itereer over alle elementen.
  • Herhaal in elke iteratie de rest van de items.
  • Ruil het huidige item met elk item dat groter is.
  • Aan het einde van elke iteratie staat het huidige item op de juiste plaats.
pakket main func Sort (items [] int) * [] int if len (items) < 2  return &items  tmp := 0 for i := 0; i < len(items); i++  for j := 0; j < len(items)-1; j++  if items[j] > items [j + 1] tmp = items [j] items [j] = items [j + 1] items [j + 1] = tmp retour & items 

De plug-in bouwen

Nu hebben we twee plug-ins die we moeten bouwen om een ​​gedeelde bibliotheek te maken die dynamisch kan worden geladen door ons hoofdprogramma. De opdracht om te bouwen is: go build -buildmode = plugin

Omdat we meerdere plug-ins hebben, heb ik ze allemaal in een aparte map onder een gedeelde map "plug-ins" geplaatst. Hier is de directory-indeling van de map met plug-ins. In elke submap van de plug-in is er het bronbestand "_plugin.go "en een beetje shell-script" build.sh "om de plug-in te bouwen. De laatste .so-bestanden gaan naar de hoofdmap" plugins ":

$ tree plug-ins plug-ins ├── bubble_sort │ ├── bubble_sort_plugin.go │ └── build.sh ├── bubble_sort_plugin.so ├── quick_sort │ ├── build.sh │ └── quick_sort_plugin.go └── quick_sort_plugin .zo

De reden dat de * .so-bestanden in de plugins-directory terechtkomen, is dat ze gemakkelijk kunnen worden ontdekt door het hoofdprogramma, zoals je later zult zien. De eigenlijke build-opdracht in elk "build.sh" -script geeft aan dat het uitvoerbestand naar de bovenliggende map moet gaan. Voor de plug-in voor bellen sorteren is dit bijvoorbeeld:

go build -buildmode = plugin -o ... /bubble_sort_plugin.so

De plug-in laden

Het laden van de plug-in vereist kennis van waar de doel-plug-ins te lokaliseren zijn (de * .so gedeelde bibliotheken). Dit kan op verschillende manieren worden gedaan:

  • argumenten uit de opdrachtregel doorgeven
  • een omgevingsvariabele instellen
  • een bekende map gebruiken
  • een configuratiebestand gebruiken

Een andere zorg is als het hoofdprogramma de plugin-namen kent of als het dynamisch alle plug-ins in een bepaalde map ontdekt. In het volgende voorbeeld verwacht het programma dat er een submap met de naam "plug-ins" onder de huidige werkdirectory zal zijn en dat alle plug-ins die het vindt worden geladen.

De oproep aan de filepath.Glob ( "plugins / *. so") functie retourneert alle bestanden met de extensie ".so" in de submap plug-ins, en plugin.Open (bestandsnaam) laadt de plugin. Als er iets misgaat, raakt het programma in paniek.

pakket main import ("fmt" "plugin" "path / filepath") func main () all_plugins, err: = filepath.Glob ("plugins / *. so") if fout! = nil paniek (err) voor _, bestandsnaam: = bereik (all_plugins) fmt.Println (bestandsnaam) p, err: = plugin.Open (bestandsnaam) if err! = nil paniek (err) 

De plug-in in een programma gebruiken

Het lokaliseren en laden van de plug-in is slechts de helft van de strijd. Het plugin-object biedt de Opzoeken() methode die een symboolnaam geeft, retourneert een interface. Je moet die interface intypen in een concreet object (bijvoorbeeld een functie als Soort()). Er is geen manier om te ontdekken welke symbolen beschikbaar zijn. Je hoeft alleen hun naam en hun type te weten, dus je kunt assert correct typen. 

Wanneer het symbool een functie is, kunt u het aanroepen zoals elke andere functie na een succesvol type beweren. Het volgende voorbeeldprogramma demonstreert al deze concepten. Het laadt dynamisch alle beschikbare plug-ins zonder te weten welke plug-ins er zijn, behalve dat ze zich in de submap "plug-ins" bevinden. Dit wordt gevolgd door het "Sort" -symbool in elke plug-in op te zoeken en te typen in een functie met de handtekening func ([] int) * [] int. Vervolgens roept het voor elke plug-in de sorteerfunctie op met een schijfje gehele getallen en drukt het resultaat af.

pakket main import ("fmt" "plugin" "path / filepath") func main () numbers: = [] int 5, 2, 7, 6, 1, 3, 4, 8 // De plug-ins (de * .so-bestanden) moeten zich in een submap 'plugins' bevinden all_plugins, err: = filepath.Glob ("plugins / *. so") if err! = nil panic (err) for _, bestandsnaam: = bereik (all_plugins) p, err: = plugin.Open (bestandsnaam) if err! = nil paniek (err) symbool, err: = p.Lookup ("Sort") if err! = nil paniek (err) sortFunc, ok = symbool. (func ([] int) * [] int) if! ok panic ("Plugin has no 'Sort ([] int) [] int' function") gesorteerd: = sortFunc (getallen) fmt.Println (bestandsnaam, gesorteerd) Uitvoer: plug-ins / bubble_sort_plugin.so & [1 2 3 4 5 6 7 8] plug-ins / quick_sort_plugin.so & [1 2 3 4 5 6 7 8] 

Conclusie

Het "plugin" -pakket biedt een goede basis voor het schrijven van geavanceerde Go-programma's die plug-ins dynamisch kunnen laden als dat nodig is. De programmeerinterface is zeer eenvoudig en vereist gedetailleerde kennis van het gebruiksprogramma op de interface van de plug-in. 

Het is mogelijk om een ​​geavanceerder en gebruikersvriendelijker plug-in framework te bouwen bovenop het "plug-in" pakket. Hopelijk wordt het binnenkort naar alle platforms geporteerd. Als u uw systemen onder Linux implementeert, overweeg dan het gebruik van plug-ins om uw programma's flexibeler en uitbreidbaarder te maken.