Guida per principianti


Questa guida ti accompegnerà attraverso la scrittura del tuo primo servizio Goa completo. Puoi trovare l’implementazione completa in questa GitHub repository. Le istruzioni assumono l’uso dei Go modules, pertanto richiedono una versione di Go installata successiva o uguale alla 1.11.

Pre-requisiti

Le istruzioni qui sotto servono a creare un nuovo progetto sotto la tua home. Puoi sostituire $HOME con qualunque altra cartella, l’unico problema è che se scegli una cartella sotto il tuo GOPATH devi essere sicuro che i moduli Go siano abilitati impostando la variabile di ambiente GO111MODULE al valore on.

cd $HOME
mkdir -p calc/design
cd calc
go mod init calc

Ora assicurati che Goa sia installato e aggiornato:

go get -u goa.design/goa/v3
go get -u goa.design/goa/v3/...

Questo servizio usa gRPC e pertanto necessita di protoc and protoc-gen-go.

  • Scarica il binario protoc dalle release.
  • Assicurati che protoc sia nel tuo path.
  • Installa il plugin protoc per Go: go get -u github.com/golang/protobuf/protoc-gen-go

Progetta

In questa sezione progetteremo la nostra API. Questo step è uno dei più caratteristici del Goa framework: Goa ti permette di pensare a come sarà la tua API senza preoccuparti di come verrà implementata e rivedere il design con tutti gli stakeholders prima di iniziare a implementare sul serio. Questo è un valore enorme, soprattutto in grandi società dove team differenti devono implementare e consumare servizi. Apri il file $HOME/calc/design/design.go e scrivi il seguente codice:

package design

import (
	. "goa.design/goa/v3/dsl"
)

var _ = API("calc", func() {
	Title("Calculator Service")
	Description("Service for multiplying numbers, a Goa teaser")
    Server("calc", func() {
        Host("localhost", func() {
            URI("http://localhost:8000")
            URI("grpc://localhost:8080")
        })
    })
})

var _ = Service("calc", func() {
	Description("The calc service performs operations on numbers.")

	Method("multiply", func() {
		Payload(func() {
			Field(1, "a", Int, "Left operand")
			Field(2, "b", Int, "Right operand")
			Required("a", "b")
		})

		Result(Int)

		HTTP(func() {
			GET("/multiply/{a}/{b}")
		})

		GRPC(func() {
		})
	})

	Files("/openapi.json", "./gen/http/openapi.json")
})

Il design descrive un servizio chiamato calc, il quale definisce a sua volta un metodo multiply. multiply prende un payload come input che consiste di 2 interi e ritorna un intero a sua volta. Esso descrive anche i mapping ai livelli di trasporto HTTP e gRPC. Il trasporto HTTP usa gli parameters per gli input mentre il gRPC usa il message (non è esplicito, ma è il comportamento di default). Sia HTTP che gRPC usano il codice di stato OK nelle risposte (anch’esso il default).

Infine, il design espone un file server HTTP che fornisce le specifiche OpenAPI generate.

Questo esempio copre solo una frazione di cosa Goa può fare. Puoi trovare più esempi nella repository apposita. La documentazione del DSL Goa elenca tutti i DSL insieme a una loro descrizione e uno o più esempi di utilizzo.

Generazione di codice

Il comando goa gen

Ora che avviamo un design per il nostro servizio, possiamo eseguire il comando goa gen per generare il codice di scaffolding. Il comando prende l’import path del design package come parametro. Accetta anche il path della directory di output, opzionalmente. Dato che il nostro design package è stato creato sotto il modulo calc il comando da eseguire è:

goa gen calc/design

Il comando stampa a video i nomi dei file che genera. Se la cartella di output non è specificata, il comando usa la cartella corrente. I file generati dovrebbero avere una struttura simile alla seguente:

gen
├── calc
│   ├── client.go
│   ├── endpoints.go
│   └── service.go
├── grpc
│   ├── calc
│   │   ├── client
│   │   │   ├── cli.go
│   │   │   ├── client.go
│   │   │   ├── encode_decode.go
│   │   │   └── types.go
│   │   ├── pb
│   │   │   ├── calc.pb.go
│   │   │   └── calc.proto
│   │   └── server
│   │       ├── encode_decode.go
│   │       ├── server.go
│   │       └── types.go
│   └── cli
│       └── calc
│           └── cli.go
└── http
    ├── calc
    │   ├── client
    │   │   ├── cli.go
    │   │   ├── client.go
    │   │   ├── encode_decode.go
    │   │   ├── paths.go
    │   │   └── types.go
    │   └── server
    │       ├── encode_decode.go
    │       ├── paths.go
    │       ├── server.go
    │       └── types.go
    ├── cli
    │   └── calc
    │       └── cli.go
    ├── openapi.json
    └── openapi.yaml

La cartella gen contiene la sotto-cartella calc dovesi trova il codice transport-independent del servizio. Il file endpoints.go crea un Goa endpoint che espone il codice transport-agnostic ai livelli di trasporto.

La cartella grpc contiene il protocol buffer file (pb/calc.proto) che descrive a livello gRPC il servizio calc, oltre che l’output del tool protoc (pb/calc.pb.go). Questa directory contiene anche il codice di server e client che si aggancia con i server e client auto generati da protoc, e ciò permette di codificare e decodificare le richieste e le risposte. Infine la cartella cli contiene il codice della CLI che costruisce le richieste gRPC da riga di comando.

La cartella http descrive il livello di trasporto HTTP, il quale definisce codice di server e client insieme alla logica di codifica e decodifica e il codice della CLI, usata per costruire richieste HTTP da riga di comando. Essa contiene anche i file di specifica Open API 2.0 sia in formato JSON che in formato YAML.

Il comando goa example

Ora possiamo eseguire il comando goa example per generare un implementazione base del servizio che usa i file precedentemente generati.

Nota: Il codice generato da goa gen non può essere editato, dato che viene rigenerato completamente ogni volta che il comando viene eseguito (per esempio quando il design cambia). Questo è intenzionale e serve a separare codice generato e non e mantenere il codice pulito usando gli standard Go. Il codice generato da goa example, comunque è codice che andrà a finire lo sviluppatore. Andrebbe modificato, testato, eccetera. Questo comando genera un punto di inizio per velocizzare lo sviluppo del servizio, e NON è pensato per essere ri-eseguito quando il design cambia. Semplicemente va aggiornato appropriatamente.

goa example calc/design

Il comando goa example crea i seguenti file:


├── calc.go
├── cmd
│   ├── calc
│   │   ├── grpc.go
│   │   ├── http.go
│   │   └── main.go
│   └── calc-cli
│       ├── grpc.go
│       ├── http.go
│       └── main.go

calc.go contiene una implementazione vuota del metodo multiply descritto dal design. L’unica cosa rimasta da fare è scrivere il codice che implementa, compilarlo, testarlo ed eseguirlo su server e client.

Apri il file calc.go e implementa il metodo Multiply:

func (s *calcsrvc) Multiply(ctx context.Context, p *calc.MultiplyPayload) (res int, err error) {
  return p.A + p.B, nil
}

Il comando goa example usa la DSL Server opzionalmente definita nel design per generare codice compilabile per server e client. Lo costruisce nella cartella cmd per ogni Server specificato nel design. Qui abbiamo definito un unico server calc che ascolta richieste HTTP sulla porta 8000.

Costruisci e Esegui il servizio

Il codice generato per server e client è costruito ed eseguito come segue:

go build ./cmd/calc && go build ./cmd/calc-cli

# Esegui il server

./calc
[calcapi] 21:35:36 HTTP "Multiply" mounted on GET /multiply/{a}/{b}
[calcapi] 21:35:36 HTTP "./gen/http/openapi.json" mounted on GET /openapi.json
[calcapi] 21:35:36 serving gRPC method calc.Calc/Multiply
[calcapi] 21:35:36 HTTP server listening on "localhost:8000"
[calcapi] 21:35:36 gRPC server listening on "localhost:8080"

# Esegui il client

# Contatta il server HTTP
$ ./calc-cli --url="http://localhost:8000" calc multiply --a 1 --b 2
2

# Contatta il server gRPC
$ ./calc-cli --url="grpc://localhost:8080" calc multiply --message '{"a": 1, "b": 2}'
2

Riassunto e Prossimi passi

Come hai potuto vedere Goa accellera lo sviluppo dei servizi permettendo di scrivere una singola sorgente di verità, da cui server, client e documentazione vengono generati automaticamente. L’abilità di concentrarsi sulla progettazione dell’API abilita un processo di sviluppo scalabile e robusto, dove i team possono revisionare e accordarsi su un API prima di iniziare l’implementazione. Una volta che il design è finito il codice generato si occupa di tutto il lavoro di codifica, decodifica e validazione (prova a chiamare il servizio calc passando valori non interi, ad esempio).

Questo esempio copre solo le basi di Goa, la Panoramica copre molti altri aspetti. Magari vuoi anche dare un’occhiata agli altri esempi. Infine, la GoDoc del package DSL include molti snippet di codice e fornisce un grande riferimento nella progettazione.