Scrivere plugin in Go

Go non può caricare il codice dinamicamente prima di Go 1.8. Sono un grande sostenitore dei sistemi basati su plugin, che in molti casi richiedono il caricamento dinamico dei plugin. Ho anche considerato a un certo punto di scrivere un pacchetto di plugin basato sull'integrazione C.

Sono davvero entusiasta del fatto che i progettisti di Go abbiano aggiunto questa funzionalità alla lingua. In questo tutorial imparerai perché i plugin sono così importanti, quali piattaforme sono attualmente supportate e come creare, creare, caricare e utilizzare plugin nei tuoi programmi.   

La logica per i plugin Go

Go plugin possono essere utilizzati per molti scopi. Ti consentono di suddividere il sistema in un motore generico facile da ragionare e testare e molti plug-in aderiscono a un'interfaccia rigorosa con responsabilità ben definite. I plugin possono essere sviluppati indipendentemente dal programma principale che li utilizza. 

Il programma può utilizzare contemporaneamente diverse combinazioni di plugin e anche più versioni dello stesso plugin. I confini nitidi tra il programma principale e i plugin promuovono le migliori pratiche di accoppiamento lento e separazione delle preoccupazioni.

Il pacchetto "plugin"

Il nuovo pacchetto "plugin" introdotto in Go 1.8 ha un ambito e un'interfaccia molto ristretti. Fornisce il Aperto() funzione per caricare una libreria condivisa, che restituisce un oggetto Plugin. L'oggetto Plugin ha un Consultare() la funzione che restituisce un simbolo (l'interfaccia vuota ) può essere asserita per una funzione o una variabile esposta dal plugin. Questo è tutto.

Supporto della piattaforma

Il pacchetto plugin è supportato solo su Linux in questo momento. Ma ci sono modi, come vedrai, per giocare con i plugin su qualsiasi sistema operativo.

Preparazione di un ambiente basato su Docker

Se stai sviluppando su una macchina Linux, devi solo installare Go 1.8 e sei a posto. Ma, se sei su Windows o macOS, hai bisogno di un container VM o Docker Linux. Per usarlo, devi prima installare Docker.

Una volta installato Docker, apri una finestra della console e digita: finestra mobile run -it -v ~ / go: / go golang: 1.8-wheezy bash

Questo comando mappa il mio locale $ GOPATH a ~ / Go a /partire all'interno del contenitore. Ciò mi consente di modificare il codice utilizzando i miei strumenti preferiti sull'host e di averlo a disposizione all'interno del contenitore per la creazione e l'esecuzione in ambiente Linux.

Per maggiori informazioni su Docker, controlla la mia serie "Docker From the Ground Up" qui su Envato Tuts +:

  • Docker da zero: informazioni sulle immagini
  • Docker da zero: costruire immagini
  • Docker da zero: lavorare con i contenitori, parte 1
  • Docker da zero: lavorare con i contenitori, parte 2

Creazione di un Go Plugin

Un plugin Go si presenta come un pacchetto normale e puoi usarlo anche come pacchetto normale. Diventa un plugin solo quando lo costruisci come plugin. Qui ci sono un paio di plugin che implementano un Ordinare()funzione che ordina una porzione di numeri interi. 

QuickSort Plugin

Il primo plugin implementa un ingenuo algoritmo QuickSort. L'implementazione funziona su sezioni con elementi unici o con duplicati. Il valore di ritorno è un puntatore a una porzione di numeri interi. Questo è utile per le funzioni di ordinamento che ordinano i loro elementi sul posto perché consente di tornare senza copiare. 

In questo caso, in realtà creo più sezioni intermedie, quindi l'effetto è per lo più sprecato. Sacrifico le prestazioni per la leggibilità qui poiché l'obiettivo è quello di dimostrare i plugin e non implementare un algoritmo super efficiente. La logica è la seguente:

  • Se ci sono zero articoli o un articolo, restituisci la porzione originale (già ordinata).
  • Scegli un elemento casuale come un piolo.
  • Aggiungi tutti gli elementi che sono meno del peg al sotto fetta.
  • Aggiungi tutti gli elementi che sono maggiori del peg al sopra fetta. 
  • Aggiungi tutti gli elementi uguali al peg al mezzo fetta.

A questo punto, il mezzo slice è ordinato perché tutti i suoi elementi sono uguali (se ci fossero duplicati del peg, qui ci saranno più elementi). Ora arriva la parte ricorsiva. Ordina il sotto e sopra fette chiamando Ordinare() ancora. Al ritorno di queste chiamate, tutte le sezioni verranno ordinate. Quindi, aggiungendoli semplicemente si ottiene un ordinamento completo della porzione originale di elementi.

pacchetto principale importazione "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: above = append (sopra, voce) below = * Sort (below) above = * Sort (sopra) ordinato: = append (append (below, middle ...), sopra ...) return & sorted

BubbleSort Plugin

Il secondo plugin implementa l'algoritmo BubbleSort in modo ingenuo. BubbleSort è spesso considerato lento, ma per un piccolo numero di elementi e con qualche piccola ottimizzazione spesso batte algoritmi più sofisticati come QuickSort. 

In realtà è comune utilizzare un algoritmo di ordinamento ibrido che inizia con QuickSort e quando la ricorsione raggiunge array abbastanza piccoli l'algoritmo passa a BubbleSort. Il plugin di ordinamento delle bolle implementa a Ordinare() funzione con la stessa firma dell'algoritmo di ordinamento rapido. La logica è la seguente:

  • Se ci sono zero articoli o un articolo, restituisci la porzione originale (già ordinata).
  • Iterate su tutti gli elementi.
  • In ogni iterazione, scorrere il resto degli elementi.
  • Scambia l'oggetto corrente con qualsiasi oggetto che è più grande.
  • Alla fine di ogni iterazione, l'oggetto corrente sarà nella posizione corretta.
pacchetto func func Sort (items [] int) * [] int if len (elementi) < 2  return &items  tmp := 0 for i := 0; i < len(items); i++  for j := 0; j < len(items)-1; j++  if items[j] > articoli [j + 1] tmp = articoli [j] articoli [j] = articoli [j + 1] articoli [j + 1] = tmp ritorno & voci 

Costruire il plugin

Ora abbiamo due plug-in che dobbiamo compilare per creare una libreria condivisibile che possa essere caricata dinamicamente dal nostro programma principale. Il comando da compilare è: vai a build -buildmode = plugin

Dato che abbiamo diversi plugin, ho inserito ciascuno in una directory separata sotto una directory "plugins" condivisa. Ecco il layout della directory della directory dei plugin. In ogni sottodirectory di plugin, c'è il file sorgente "_plugin.go "e un piccolo script di shell" build.sh "per compilare il plug-in. I file .so finali vengono inseriti nella directory" plugins "principale:

$ tree plugins plugin ├── bubble_sort │ ├── bubble_sort_plugin.go │ └── build.sh ├── bubble_sort_plugin.so ├── quick_sort │ ├── build.sh │ └── quick_sort_plugin.go └── quick_sort_plugin .così

Il motivo per cui i file * .so entrano nella directory dei plugin è che possono essere facilmente scoperti dal programma principale, come vedremo più avanti. Il comando di compilazione reale in ogni script "build.sh" specifica che il file di output deve andare nella directory padre. Ad esempio, per il plugin bubble sort è:

vai a build -buildmode = plugin -o ... /bubble_sort_plugin.so

Caricamento del plugin

Il caricamento del plugin richiede la conoscenza di dove posizionare i plugin di destinazione (le librerie condivise * .so). Questo può essere fatto in vari modi:

  • passare gli argomenti della riga di comando
  • impostazione di una variabile di ambiente
  • utilizzando una directory ben nota
  • utilizzando un file di configurazione

Un'altra preoccupazione è se il programma principale conosce i nomi dei plugin o se scopre in modo dinamico tutti i plugin in una determinata directory. Nell'esempio seguente, il programma si aspetta che ci sia una sottodirectory chiamata "plugins" nella directory di lavoro corrente, e carica tutti i plugin che trova.

La chiamata al filepath.Glob ( "plugins / *. così") funzione restituisce tutti i file con l'estensione ".so" nella sottodirectory dei plugin, e plugin.Open (nome del file) carica il plugin. Se qualcosa va storto, il programma va in panico.

pacchetto main import ("fmt" "plugin" "percorso / filepath") func main () all_plugins, err: = filepath.Glob ("plugins / *. so") se err! = nil panic (err) per _, filename: = range (all_plugins) fmt.Println (nomefile) p, err: = plugin.Open (nomefile) se err! = nil panic (err) 

Uso del plugin in un programma

Individuare e caricare il plug-in è solo metà della battaglia. L'oggetto plugin fornisce il Consultare() il metodo che ha un nome simbolico restituisce un'interfaccia. Devi digitare asserire quell'interfaccia in un oggetto concreto (ad esempio una funzione come Ordinare()). Non c'è modo di scoprire quali simboli sono disponibili. Devi solo conoscere i loro nomi e il loro tipo, in modo da poter digitare affermare correttamente. 

Quando il simbolo è una funzione, puoi invocarla come qualsiasi altra funzione dopo che un tipo riuscito ha affermato. Il seguente programma di esempio mostra tutti questi concetti. Carica dinamicamente tutti i plugin disponibili senza sapere quali plugin sono presenti, tranne che si trovano nella sottodirectory "plugins". Segue cercando il simbolo "Ordina" in ciascun plugin e digita il valore in una funzione con la firma func ([] int) * [] int. Quindi, per ciascun plug-in, richiama la funzione di ordinamento con una porzione di numeri interi e stampa il risultato.

pacchetto main import ("fmt" "plugin" "percorso / filepath") func main () numbers: = [] int 5, 2, 7, 6, 1, 3, 4, 8 // I plugin (il * .so files) deve essere in una sottodirectory 'plugins' all_plugins, err: = filepath.Glob ("plugins / *. so") se err! = nil panic (err) per _, filename: = range (all_plugins) p, err: = plugin.Open (nome file) se err! = nil panic (err) symbol, err: = p.Lookup ("Sort") se err! = nil panic (err) sortFunc, ok = symbol. (func ([] int) * [] int) if! ok panic ("Il plugin non ha 'Sort ([] int) [] int' function") ordinato: = sortFunc (numeri) fmt.Println (filename, sorted) Output: plugins / bubble_sort_plugin.so e [1 2 3 4 5 6 7 8] plugins / quick_sort_plugin.so e [1 2 3 4 5 6 7 8] 

Conclusione

Il pacchetto "plugin" fornisce una grande base per la scrittura di sofisticati programmi Go che possono caricare dinamicamente i plugin, se necessario. L'interfaccia di programmazione è molto semplice e richiede una conoscenza dettagliata del programma utilizzando l'interfaccia del plugin. 

È possibile costruire un framework di plugin più avanzato e user-friendly in cima al pacchetto "plugin". Si spera che sarà presto trasferito su tutte le piattaforme. Se distribuisci i tuoi sistemi su Linux, considera l'utilizzo di plug-in per rendere i tuoi programmi più flessibili ed estendibili.