La generazione del codice gRPC crea un’implementazione completa di client e server
che gestisce tutti gli aspetti a livello di trasporto. Goa genera una definizione
Protobuf per ogni servizio e chiama automaticamente protoc
per generare il
codice di basso livello del server e del client. Goa genera anche codice che
sfrutta il codice generato da Protobuf per creare un’implementazione di alto
livello del server e del client gRPC.
La definizione del servizio protobuf viene generata in
gen/grpc/<nome del servizio>/pb/goagen_<nome dell'api>_<nome del servizio>.proto
:
syntax = "proto3";
package calc;
service Calc {
rpc Add (AddRequest) returns (AddResponse);
rpc Multiply (MultiplyRequest) returns (MultiplyResponse);
}
message AddRequest {
int64 a = 1;
int64 b = 2;
}
message AddResponse {
int64 result = 1;
}
// ...altri messaggi...
Questa definizione protobuf viene utilizzata per generare il codice gRPC di basso
livello tramite il compilatore protoc
, che Goa invoca automaticamente durante
la generazione del codice.
Goa genera un’implementazione completa del server gRPC in
gen/grpc/<nome del servizio>/server/server.go
che espone i metodi del servizio
utilizzando gRPC. Il server può essere istanziato utilizzando la funzione New
generata che accetta gli endpoint del servizio e un gestore unario opzionale. Se
non viene fornito alcun gestore unario, il server utilizzerà il gestore
predefinito fornito da Goa.
// New istanzia la struttura del server con gli endpoint del servizio calc.
func New(e *calc.Endpoints, uh goagrpc.UnaryHandler) *Server {
return &Server{
AddH: NewAddHandler(e.Add, uh),
MultiplyH: NewMultiplyHandler(e.Multiply, uh),
}
}
Nel codice sopra goagrpc
si riferisce al pacchetto Goa gRPC situato in
goa.design/goa/v3/grpc
. Il tipo UnaryHandler
è una funzione che prende un
contesto e una richiesta e restituisce una risposta e un errore. Se il servizio
espone metodi di streaming, New
accetta anche un gestore di streaming.
La struttura Server
espone campi che possono essere utilizzati per modificare
singoli gestori o applicare middleware a specifici endpoint:
// Server elenca i gestori gRPC degli endpoint del servizio calc.
type Server struct {
AddH goagrpc.UnaryHandler
MultiplyH goagrpc.UnaryHandler
// ... Campi privati ...
}
Il resto del file del server implementa i gestori del server gRPC per ogni metodo del servizio. Questi gestori sono responsabili della decodifica della richiesta, della chiamata al metodo del servizio e della codifica della risposta e dell’errore.
L’interfaccia del servizio e i livelli degli endpoint sono i blocchi fondamentali del tuo servizio generato da Goa. Il tuo pacchetto main utilizzerà questi livelli per creare le implementazioni specifiche del server e del client per il trasporto, permettendoti di eseguire il tuo servizio e interagire con esso utilizzando gRPC:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"github.com/<tuo username>/calc"
gencalc "github.com/<tuo username>/calc/gen/calc"
genpb "github.com/<tuo username>/calc/gen/grpc/calc/pb"
gengrpc "github.com/<tuo username>/calc/gen/grpc/calc/server"
)
func main() {
svc := calc.New() // La tua implementazione del servizio
endpoints := gencalc.NewEndpoints(svc) // Crea gli endpoint del servizio
svr := grpc.NewServer(nil) // Crea il server gRPC
gensvr := gengrpc.New(endpoints, nil) // Crea l'implementazione del server
genpb.RegisterCalcServer(svr, genserver) // Registra il server con gRPC
lis, _ := net.Listen("tcp", ":8080") // Avvia il listener del server gRPC
svr.Serve(lis) // Avvia il server gRPC
}
Goa genera un’implementazione completa del client gRPC in
gen/grpc/<nome del servizio>/client/client.go
. Simile al client HTTP, fornisce
metodi che creano endpoint Goa per ogni metodo del servizio, che possono poi
essere avvolti in implementazioni client indipendenti dal trasporto.
La funzione NewClient
generata crea un oggetto che può essere utilizzato per
creare endpoint client indipendenti dal trasporto:
// NewClient istanzia i client gRPC per tutti gli endpoint del servizio calc.
func NewClient(cc *grpc.ClientConn, opts ...grpc.CallOption) *Client {
return &Client{
grpccli: calcpb.NewCalcClient(cc),
opts: opts,
}
}
La funzione richiede una connessione gRPC (*grpc.ClientConn
) che viene
utilizzata per creare il client protobuf di basso livello. Accetta anche opzioni
di chiamata gRPC opzionali (...grpc.CallOption
) che vengono utilizzate per
configurare il client gRPC.
La struttura Client
istanziata espone metodi che costruiscono endpoint
indipendenti dal trasporto:
// Add restituisce un endpoint che effettua richieste gRPC al server
// add del servizio calc.
func (c *Client) Add() goa.Endpoint
// Multiply restituisce un endpoint che effettua richieste gRPC al server
// multiply del servizio calc.
func (c *Client) Multiply() goa.Endpoint
Ecco un esempio di come creare e utilizzare il client gRPC per il servizio calc
:
package main
import (
"context"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
gencalc "github.com/<tuo username>/calc/gen/calc"
genclient "github.com/<tuo username>/calc/gen/grpc/calc/client"
)
func main() {
// Crea la connessione gRPC
conn, err := grpc.Dial("localhost:8080",
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Crea il client gRPC
grpcClient := genclient.NewClient(conn)
// Crea il client endpoint
client := gencalc.NewClient(
grpcClient.Add(), // Endpoint Add
grpcClient.Multiply(), // Endpoint Multiply
)
// Chiama i metodi del servizio
result, err := client.Add(context.Background(), &gencalc.AddPayload{A: 1, B: 2})
if err != nil {
log.Fatal(err)
}
log.Printf("1 + 2 = %d", result)
}
Questo esempio mostra come:
Il client gRPC gestisce tutti gli aspetti a livello di trasporto mentre il client endpoint fornisce un’interfaccia pulita per effettuare chiamate al servizio.
Il client gRPC generato fornisce un modo conveniente per interagire con il servizio utilizzando gRPC. Il client gestisce tutte le complessità della comunicazione gRPC fornendo un’interfaccia coerente che corrisponde ad altre implementazioni di trasporto.