1. Introdução
Neste codelab, você vai usar o gRPC-Go para criar um cliente e um servidor que formam a base de um aplicativo de mapeamento de rotas escrito em Go.
Ao final do tutorial, você terá um cliente que se conecta a um servidor remoto usando o gRPC para receber o nome ou o endereço postal do que está localizado em coordenadas específicas em um mapa. Um aplicativo completo pode usar esse design cliente-servidor para enumerar ou resumir pontos de interesse ao longo de um trajeto.
O serviço é definido em um arquivo Protocol Buffers, que será usado para gerar código boilerplate para o cliente e o servidor, permitindo que eles se comuniquem entre si e economizando tempo e esforço na implementação dessa funcionalidade.
Esse código gerado cuida não apenas das complexidades da comunicação entre o servidor e o cliente, mas também da serialização e desserialização de dados.
O que você vai aprender
- Como usar buffers de protocolo para definir uma API de serviço.
- Como criar um cliente e um servidor baseados em gRPC com uma definição de buffers de protocolo usando a geração automática de código.
- Entendimento da comunicação cliente-servidor com gRPC.
Este codelab é destinado a desenvolvedores Go que não conhecem o gRPC ou querem relembrar o assunto, além de qualquer pessoa interessada em criar sistemas distribuídos. Não é necessário ter experiência com gRPC.
2. Antes de começar
Pré-requisitos
Verifique se você instalou o seguinte:
- A versão 1.24.5 ou mais recente da cadeia de ferramentas do Go. Para instruções de instalação, consulte Primeiros passos do Go.
- O compilador de buffers de protocolo,
protoc
, versão 3.27.1 ou mais recente. Para instruções de instalação, consulte o guia de instalação do compilador. - Os plug-ins do compilador de buffer de protocolo para Go e gRPC. Para instalar esses plug-ins, execute os seguintes comandos:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Atualize a variável PATH
para que o compilador de buffer de protocolo possa encontrar os plug-ins:
export PATH="$PATH:$(go env GOPATH)/bin"
Acessar o código
Para que você não precise começar do zero, este codelab oferece um scaffold do código-fonte do aplicativo para você concluir. As etapas a seguir mostram como concluir o aplicativo, incluindo o uso dos plug-ins do compilador de buffer de protocolo para gerar o código gRPC boilerplate.
Faça o download do código-fonte como um arquivo .ZIP no GitHub e descompacte o conteúdo.
Como alternativa, o código-fonte completo está disponível no GitHub se você quiser pular a digitação de uma implementação.
3. Definir o serviço
A primeira etapa é definir o serviço gRPC do aplicativo, o método RPC e os tipos de mensagens de solicitação e resposta usando buffers de protocolo. Seu serviço vai oferecer:
- Um método RPC chamado
GetFeature
que o servidor implementa e o cliente chama. - Os tipos de mensagem
Point
eFeature
, que são estruturas de dados trocadas entre o cliente e o servidor ao usar o métodoGetFeature
. O cliente fornece coordenadas do mapa como umPoint
na solicitaçãoGetFeature
ao servidor, e o servidor responde com umFeature
correspondente que descreve o que está localizado nessas coordenadas.
Esse método RPC e os tipos de mensagem dele serão definidos no arquivo routeguide/route_guide.proto
do código-fonte fornecido.
Os buffers de protocolo são conhecidos como protobufs. Para mais informações sobre a terminologia do gRPC, consulte Conceitos principais, arquitetura e ciclo de vida do gRPC.
Tipos de mensagem
No arquivo routeguide/route_guide.proto
do código-fonte, primeiro defina o tipo de mensagem Point
. Um Point
representa um par de coordenadas de latitude e longitude em um mapa. Neste codelab, use números inteiros para as coordenadas:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Os números 1
e 2
são IDs exclusivos para cada um dos campos na estrutura message
.
Em seguida, defina o tipo de mensagem Feature
. Um Feature
usa um campo string
para o nome ou endereço postal de algo em um local especificado por um Point
:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Método de serviço
O arquivo route_guide.proto
tem uma estrutura service
chamada RouteGuide
que define um ou mais métodos fornecidos pelo serviço do aplicativo.
Adicione o método rpc
GetFeature
à definição RouteGuide
. Como explicado anteriormente, esse método vai pesquisar o nome ou endereço de um local em um determinado conjunto de coordenadas. Portanto, faça com que GetFeature
retorne um Feature
para um determinado Point
:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Esse é um método RPC unário: um RPC simples em que o cliente envia uma solicitação ao servidor e aguarda uma resposta, assim como uma chamada de função local.
4. Gerar o código do cliente e do servidor
Em seguida, gere o código gRPC boilerplate para o cliente e o servidor do arquivo .proto
usando o compilador de buffer de protocolo. No diretório routeguide
, execute:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
Esse comando gera os seguintes arquivos:
route_guide.pb.go
, que contém funções para criar os tipos de mensagens do aplicativo e acessar os dados deles.route_guide_grpc.pb.go
, que contém funções usadas pelo cliente para chamar o método gRPC remoto do serviço e funções usadas pelo servidor para fornecer esse serviço remoto.
Em seguida, vamos implementar o método GetFeature
no lado do servidor para que, quando o cliente enviar uma solicitação, o servidor possa responder com uma resposta.
5. Implementar o serviço
A função GetFeature
no lado do servidor é onde o trabalho principal é feito: ela recebe uma mensagem Point
do cliente e retorna em uma mensagem Feature
as informações de local correspondentes de uma lista de lugares conhecidos. Confira a implementação da função em server/server.go
:
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
for _, feature := range s.savedFeatures {
if proto.Equal(feature.Location, point) {
return feature, nil
}
}
// No feature was found, return an unnamed feature
return &pb.Feature{Location: point}, nil
}
Quando esse método é invocado após uma solicitação de um cliente remoto, a função recebe um objeto Context
que descreve a chamada de RPC e um objeto de buffer de protocolo Point
da solicitação do cliente. A função retorna um objeto de buffer de protocolo Feature
para o local pesquisado e um error
, conforme necessário.
No método, preencha um objeto Feature
com as informações adequadas para o Point
especificado e, em seguida, return
com um erro nil
para informar ao gRPC que você terminou de lidar com o RPC e que o objeto Feature
pode ser retornado ao cliente.
O método GetFeature
exige que um objeto routeGuideServer
seja criado e registrado para que as solicitações de clientes para pesquisas de local possam ser encaminhadas a essa função. Isso é feito em main()
:
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
s := &routeGuideServer{}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)
}
Veja o que está acontecendo em main()
, etapa por etapa:
- Especifique a porta TCP a ser usada para detectar solicitações de clientes remotos usando
lis, err := net.Listen(...)
. Por padrão, o aplicativo usa a porta TCP50051
, conforme especificado pela variávelport
ou transmitindo a chave--port
na linha de comando ao executar o servidor. Se não for possível abrir a porta TCP, o aplicativo será encerrado com um erro fatal. - Crie uma instância do servidor gRPC usando
grpc.NewServer(...)
e nomeie essa instância comogrpcServer
. - Crie um ponteiro para
routeGuideServer
, uma estrutura que representa o serviço de API do aplicativo, nomeando o ponteiros.
. - Use
s.loadFeatures()
para preencher a matrizs.savedFeatures
com locais que podem ser pesquisados usandoGetFeature
. - Registre o serviço de API com o servidor gRPC para que as chamadas de RPC para
GetFeature
sejam encaminhadas para a função apropriada. - Chame
Serve()
no servidor com os detalhes da porta para fazer uma espera de bloqueio para solicitações do cliente. Isso continua até que o processo seja encerrado ouStop()
seja chamado.
A função loadFeatures()
recebe os mapeamentos de coordenadas para localizações de server/testdata.go
.
6. Criar o cliente
Agora edite client/client.go
, que é onde você vai implementar o código do cliente.
Para chamar os métodos do serviço remoto, primeiro precisamos criar um canal gRPC para se comunicar com o servidor. Para isso, transmitimos a string URI de destino do servidor (que, neste caso, é simplesmente o endereço e o número da porta) para grpc.NewClient()
na função main()
do cliente da seguinte maneira:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
O endereço do servidor, definido pela variável serverAddr
, é localhost:50051
por padrão e pode ser substituído pela chave --addr
na linha de comando ao executar o cliente.
Se o cliente precisar se conectar a um serviço que exige credenciais de autenticação, como TLS ou JWT, ele poderá transmitir um objeto DialOptions
como parâmetro para grpc.NewClient
que contenha as credenciais necessárias. O serviço RouteGuide
não exige credenciais.
Depois que o canal gRPC é configurado, precisamos de um stub de cliente para realizar RPCs por chamadas de função Go. Recebemos esse stub usando o método NewRouteGuideClient
fornecido pelo arquivo route_guide_grpc.pb.go
gerado do arquivo .proto
do aplicativo.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
Chamar métodos de serviço
No gRPC-Go, os RPCs operam em um modo de bloqueio/síncrono, o que significa que a chamada de RPC aguarda a resposta do servidor e retorna uma resposta ou um erro.
RPC simples
Chamar a RPC simples GetFeature
é quase tão simples quanto chamar um método local, neste caso client.GetFeature
:
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
O cliente chama o método no stub criado anteriormente. Para os parâmetros do método, o cliente cria e preenche um objeto de buffer de protocolo de solicitação Point
. Você também transmite um objeto context.Context
, que permite mudar o comportamento da RPC, se necessário, como definir um limite de tempo para a chamada ou cancelar uma RPC em andamento. Se a chamada não retornar um erro, o cliente poderá ler as informações de resposta do servidor no primeiro valor de retorno:
log.Println(feature)
No total, a função main()
do cliente vai ficar assim:
func main() {
flag.Parse()
// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
// Create a new RouteGuide stub.
client := pb.NewRouteGuideClient(conn)
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
log.Println(feature)
}
7. Faça um teste
Confirme se o servidor e o cliente estão funcionando corretamente executando os seguintes comandos no diretório de trabalho do aplicativo:
- Execute o servidor em um terminal:
cd server go run .
- Execute o cliente em outro terminal:
cd client go run .
Você vai ver uma saída como esta, com carimbos de data/hora omitidos para maior clareza:
Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
8. A seguir
- Saiba como o gRPC funciona em Introdução ao gRPC e Conceitos principais.
- Siga as etapas do tutorial de noções básicas.
- Confira a referência da API.