Neste tutorial, cobriremos os fundamentos do gRPC, uma estrutura RPC universal de código aberto e de alto desempenho, revisaremos um pouco sobre a linguagem de programação Dart e demonstraremos como construir um servidor gRPC no Dart.
Orientaremos você no seguinte:
O que é gRPC?
gRPC é uma estrutura de comunicação entre processos (RPC) desenvolvida pelo Google e lançada em 2015. Ele é de código aberto, com neutralidade de idioma e tem um tamanho binário compacto. O gRPC também oferece suporte a HTTP/2 e é compatível com várias plataformas.
gRPC é muito diferente do RPC convencional no sentido de que usa Protocol Buffers como seu IDL para definir sua interface de serviço. Buffers de protocolo é uma ferramenta de serialização desenvolvida pelo Google que permite definir suas estruturas de dados e, em seguida, usar o compilador de buffer de protocolo para gerar o código-fonte dessas estruturas de dados para a linguagem de sua escolha. A linguagem gerada é usada para escrever e ler as estruturas de dados de e para qualquer contexto que desejarmos. De acordo com os documentos oficiais , “Os buffers de protocolo são a plataforma neutra de linguagem do Google-mecanismo neutro e extensível para serializar dados estruturados-pense em XML, mas menor, mais rápido e mais simples. ”
O buffer de protocolo é usado para escrever a interface de definição de serviço, que é usada para definir estruturas e métodos de dados. As estruturas de dados são como tipos de dados em linguagens com tipos estáticos, como Java; eles dizem ao compilador/intérprete como os dados devem ser usados. As estruturas de dados na interface de definição de serviço são os tipos de argumento que serão passados para os métodos e os tipos de retorno dos métodos. Esta interface de definição de serviço é mantida em um arquivo de texto com extensão .proto
. Os métodos na interface de serviço são os métodos que o servidor gRPC expõe para serem chamados por clientes gRPC.
gRPC tem três componentes:
-
servidor
hospeda a implementação dos métodos e escuta as solicitações dos clientes -
buffer de protocolo
contém o formato da mensagem das estruturas de dados e os métodos, incluindo seus argumentos e tipo de retorno -
cliente
chama os métodos hospedados pelo servidor. O cliente conhece os métodos e seus tipos de retorno e argumento da interface de definição de serviço no arquivoproto
Usando essa interface de serviço, o servidor gRPC configura seu código de servidor implementando os métodos na interface de serviço. Ele se configura e escuta as solicitações (chamadas de método) dos clientes.
O cliente usa a interface de definição de serviço para gerar o stub do cliente. Esta sub-rotina de cliente é de onde os métodos no servidor são chamados. Um aplicativo cliente gRPC pode fazer solicitações diretas a um servidor aplicativo. Tanto o cliente quanto o servidor adotam uma interface comum, como um contrato, em que determina quais métodos, tipos e retornos cada uma das operações terá.
Como funcionam os buffers de protocolo
O mais atraente sobre o gRPC é o uso do buffer de protocolo, que permite que o protocolo seja independente de plataforma e poliglota. Isso significa que o servidor pode ser escrito em um determinado idioma e o cliente desenvolvido em outro idioma. O buffer de protocolo torna isso possível porque tem compiladores que podem gerar um código-fonte de linguagem a partir da estrutura de dados em suas definições.
Por exemplo, digamos que o servidor seja escrito em JavaScript. Usaremos o proto-compilador para gerar o código-fonte JavaScript a partir das definições no arquivo .proto
. O servidor pode acessar e manipular as estruturas de dados e métodos usando o código JavaScript.
Para o cliente, queremos que seja desenvolvido em Java, portanto, geraremos o código-fonte Java a partir das definições. O cliente pode então chamar os métodos e acessar as estruturas de dados usando o código Java. É isso que queremos dizer quando afirmamos que gRPC é poliglota e independente de plataforma.
Observe que os buffers de protocolo não são usados apenas pelo gRPC. Eles também podem ser usados para serialização. É comumente usado para enviar dados por meio de streams para que você possa ler e gravar sua estrutura de dados sem qualquer perda de sobrecarga.
Construindo um servidor gRPC no Dart
Agora que entendemos os fundamentos do gRPC e dos buffers de protocolo, é hora de construir nosso servidor gRPC em Dart .
Antes de começar, certifique-se de ter o Dart SDK instalado em sua máquina. O executável Dart deve estar disponível globalmente em seu sistema. Execute o seguinte comando para verificar:
➜ grpc-dart dart--version Versão do Dart SDK: 2.10.5 (estável) (terça, 19 de janeiro 13:05:37 2021 +0100) em"macos_x64"
Também precisaremos de algumas ferramentas de protocolo. Como estamos desenvolvendo o servidor gRPC no Dart, teremos que instalar o proto-compilador para o idioma do Dart. Este compilador irá gerar o código-fonte Dart a partir das definições de serviço no arquivo .proto
.
O p rotocol buffer compiler é uma ferramenta de linha de comando para compilar o código IDL nos arquivos .proto
e gerar o código-fonte do idioma especificado para ele. Para obter instruções de instalação, consulte os documentos do gRPC . Certifique-se de fazer o download da versão 3 .
Finalmente, o plugin Dart para o compilador protoc gera o código-fonte do Dart a partir do código IDL nos arquivos. proto
.
Para usuários do Mac, instale o plug-in Dart protoc executando o seguinte comando:
dart pub global activate protoc_plugin
Isso instala o protoc_plugin
globalmente em sua máquina.
A seguir, atualize o $ PATH
para que o protoc
veja nosso plugin:
export PATH="$ PATH: $ HOME/.pub-cache/bin"
Agora é hora de criar o servidor.
Para nossa demonstração, criaremos um servidor gRPC que gerencia um serviço de livros. Este serviço irá expor métodos que serão usados para:
- Obtenha todos os livros (
GetAllBooks
) - Obtenha um livro do servidor por meio de seu ID (
GetBook
) - Excluir um livro (
DeleteBook
) - Editar um livro (
EditBook
) - Crie um livro (
CreateBook
)
Nosso projeto Dart será um projeto console-simple
. Execute o seguinte comando para criar o scaffold do projeto Dart:
dart create--template=console-simple dart_grpc
O subcomando create
informa ao executável Dart que desejamos criar um projeto Dart. --template=console-simple
diz ao exe Dart que queremos que o projeto Dart seja um aplicativo de console simples.
O resultado será o seguinte:
Criando/Users/.../dart_grpc usando template console-simple... .gitignore CHANGELOG.md README.md analysis_options.yaml bin/dart_grpc.dart pubspec.yaml Running pub get... 10.2s Resolvendo dependências... Baixando pedante 1.9.2... Baixando meta 1.2.4... 2 dependências alteradas! Projeto criado dart_grpc! Para começar, digite: cd dart_grpc ➜
Nosso projeto residirá na pasta dart_grpc
.
Abra o arquivo pubspec.yaml
. É aqui que definimos as configurações e dependências de um aplicativo Dart. Queremos instalar as dependências grpc
e protobuf
. Adicione a linha abaixo no arquivo pubspec.yaml
e salve:
dependências
: grpc: protobuf:
Agora, execute pub get
em seu console para que as dependências sejam instaladas.
Escrevendo a definição de erviço s
Definimos nossas definições de serviço em um arquivo .proto
. Então, vamos criar um arquivo book.proto
.
toque em book.proto
Adicione o código Protobuf
abaixo no arquivo book.proto
:
sintaxe="proto3"; service BookMethods { rpc CreateBook (Book) retorna (Book); rpc GetAllBooks (Vazio) retorna (Livros); rpc GetBook (BookId) retorna (Book); rpc DeleteBook (BookId) retorna (vazio) {}; rpc EditBook (Book) retorna (Book) {}; } mensagem vazia {} message BookId { int32 id=1; } livro de mensagens { int32 id=1; string title=2; } message Books { livros repetidos do livro=1; }
Isso é muito código. Vamos repassar linha por linha.
sintaxe="proto3";
Aqui, estamos informando ao compilador do buffer de protocolo que usaremos a versão 3 do idioma do buffer de protocolo.
service BookMethods { rpc CreateBook (Book) retorna (Book); rpc GetAllBooks (Vazio) retorna (Livros); rpc GetBook (BookId) retorna (Book); rpc DeleteBook (BookId) retorna (vazio) {}; rpc EditBook (Book) retorna (Book) {}; }
Aqui, estamos declarando os métodos e o serviço sob o qual eles estarão. A palavra-chave serviço
denota um único serviço em um gRPC, então criamos um serviço BookMethods
. Para chamar um método, o método deve ser referenciado por seu serviço. Isso é análogo aos métodos de classe
e
; métodos
são chamados por meio de sua instância de classe. Podemos ter vários serviços definidos em um proto.
Os métodos são denotados dentro de cada serviço pela palavra-chave rpc
. O rpc
diz ao compilador que o método é um endpoint rpc
e será exposto e chamado de clientes remotamente. Em nossa definição, temos cinco métodos dentro do serviço BookMethods
: CreateBook
, GetAllBooks
, GetBook
, DeleteBook
e EditBook
.
-
CreateBook
pega um tipo de dadosBook
como arg e retorna um tipo deBook
. A implementação deste método criará um novo livro -
GetAllBooks
recebe um tipoVazio
como arg e retorna um tipoLivros
. Sua implementação retornará todos os livros -
GetBook
aceita um parâmetro de entrada do tipo,BookId
e retorna umBook
. Sua implementação retornará um livro específico -
DeleteBook
pega um tipoBookId
como parâmetro de entrada e retorna um tipoVazio
. Sua implementação excluirá uma entrada de livro da coleção -
EditBook
pega um tipo deBook
como arg e retorna um tipo deBook
. Sua implementação modificará um livro da coleção
O método
Todos os outros dados deste ponto para baixo representam os dados ou tipos de mensagem. Temos:
mensagem vazia {}
A palavra-chave mensagem
denota tipos de mensagem. Cada tipo de mensagem possui campos e cada campo possui um número para identificá-lo exclusivamente no tipo de mensagem.
Vazio
denota uma estrutura de dados vazia. Isso é usado quando não queremos enviar nenhum argumento para os métodos rpc
ou quando os métodos não retornam nenhum valor. É o mesmo que void
em C/C ++.
message BookId { int32 id=1; }
Esta estrutura de dados representa um objeto de mensagem de ID de livro. O campo id
conterá um número inteiro pela palavra-chave int32
antes dele. O campo id
conterá a ID de um livro.
livro de mensagens { int32 id=1; string title=2; }
Esta estrutura de dados representa um livro. O campo id
contém a ID exclusiva do livro e o título
contém o título do livro. O campo title
será uma string seguida pela palavra-chave string
antes dela.
mensagem Livros { livros repetidos do livro=1; }
Isso representa uma série de livros. O campo books
é uma matriz que contém livros. repetido
denota um campo que será uma lista ou um array. O Livro
antes do nome do campo indica que a matriz será do tipo Livro
.
Agora que terminamos de escrever nossa definição de serviço, vamos compilar o arquivo book.proto
.
Compilando proto
A ferramenta protoc é usada para compilar nossos arquivos .proto
. Certifique-se de que a ferramenta protoc esteja globalmente disponível em seu sistema:
protoc-versão libprotoc 3.15.8
Essa é a versão da minha ferramenta de protocolo no momento em que este livro foi escrito. sua versão pode ser diferente, não importa.
Agora, certifique-se de que seu terminal esteja aberto na pasta raiz dart_grpc
. Execute o comando abaixo para compilar o arquivo book.proto
:
protoc-I=.--dart_out=grpc:. livro.proto
O I=.
informa ao compilador a pasta de origem que campo proto
estamos tentando compilar.
O subcomando dart_out=grpc:.
informa ao compilador protoc que estamos gerando o código-fonte do Dart a partir das definições book.proto
e usando-o para gRPC =grpc:
. O .
diz ao compilador para gravar os arquivos dart na pasta raiz a partir da qual estamos operando.
Este comando irá gerar os seguintes arquivos:
book.pb.dart
book.pbenum.dart
book.pbgrpc.dart
book.pbjson.dart
O arquivo mais importante é o book.pb.dart
, que contém o código-fonte do Dart para as estruturas de dados da mensagem no arquivo book.proto
. Ele também contém classes Dart para Empty
, BookId
, Book
e Books
. A partir deles, criamos suas instâncias e as usamos ao chamar os métodos rpc
.
O arquivo book.grpc.dart
contém a classe BookMethodClient
, que usaremos para criar instâncias para chamar os métodos rpc
e uma interface BookMethodsServiceBase
. Esta interface será implementada pelo servidor para adicionar as implementações dos métodos.
A seguir, escreveremos nosso código de servidor.
Criação do servidor gRPC
Vamos escrever nosso código do servidor gRPC no arquivo dart_grpc.dart
. Abra o arquivo e cole o código abaixo:
import'package: grpc/grpc.dart'; import'package: grpc/src/server/call.dart'; import'./../book.pb.dart'; import'./../book.pbgrpc.dart'; class BookMethodsService extends BookMethodsServiceBase { Livros livros=Livros (); @sobrepor FuturocreateBook (chamada ServiceCall, solicitação de Book) async { var book=Book (); book.title=request.title; book.id=request.id; books.books.add (livro); livro de devolução; } @sobrepor Futuros getAllBooks (chamada de ServiceCall, solicitação vazia) async { devolver livros; } @sobrepor Futuro getBook (chamada ServiceCall, solicitação BookId) async { var book=books.books.firstWhere ((book)=> book.id==request.id); livro de devolução; } @sobrepor Future deleteBook (chamada ServiceCall, solicitação BookId) async { books.books.removeWhere ((livro)=> book.id==request.id); return Empty (); } @sobrepor Futuro editBook (chamada ServiceCall, solicitação de Book) async { var book=books.books.firstWhere ((book)=> book.id==request.id); book.title=request.title; livro de devolução; } } Futuro main (List args) assíncrono { servidor final=servidor ( [BookMethodsService ()], const [], CodecRegistry (codecs: const [GzipCodec (), IdentityCodec ()]), ); aguarda server.serve (porta: 50051); print ('Servidor ouvindo na porta $ {server.port}...'); }
Que pedaço de código! Parece assustador, mas é mais simples do que você imagina.
A primeira parte importa os arquivos necessários. Importamos o código grpc
e o código grpc
Dart. Importamos os arquivos book.pb.dart
e book.pbgrpc.dart
porque precisamos das classes neles.
A seguir, estendemos a interface BookMethodsServiceBase
em BookMethodsService
para fornecer as implementações para todos os métodos no serviço BookMethods
.
Na classe BookMethodsService
, substituímos todos os métodos para fornecer suas implementações. Observe os dois parâmetros nos métodos. O primeiro parâmetro, ServiceCall call
, contém meta-informações sobre a solicitação. O segundo parâmetro contém a informação enviada, que é o tipo de dado que o método rpc
aceitará como argumento.
Livros livros=Livros ();
O comando acima define uma matriz books
.
No método createBook
, criamos um novo Book
, definimos o id
, title
e adicionamos para a matriz books
na variável books
.
No método getAllBooks
, acabamos de retornar a variável books
.
No método getBook
, buscamos o ID do objeto BookId request
e o usamos para obter o livro da matriz books
usando o método List # firstWhere
e retorne-o.
Em deleteBook
, retiramos o bookID da solicitação BookId
e o usamos como cursor para remover o livro da matriz books
usando o Método List # removeWhere
.
No método editBook
, o argumento request
contém as informações do Book
. Recuperamos o livro da matriz books
e editamos seu valor de propriedade title
para aquele enviado no argumento request
.
Finalmente, configuramos o servidor na função main
. Passamos a instância BookMethodsService
em uma matriz para o construtor Server
. Então, chamamos o método serve
para iniciar o servidor na porta 50051
.
Agora vamos construir o cliente.
Criação de um cliente gRPC
Crie um arquivo client.dart
dentro da pasta bin
:
touch bin/client.dart
Abra-o e cole o seguinte código:
import'package: grpc/grpc.dart'; import'./../book.pb.dart'; import'./../book.pbgrpc.dart'; class Client { Canal ClientChannel; Stub BookMethodsClient; Futuromain (List args) assíncrono { channel=ClientChannel ('localhost', porta: 50051, options://Sem credenciais neste exemplo const ChannelOptions (credenciais: ChannelCredentials.insecure ())); stub=BookMethodsClient (canal, opções: CallOptions (tempo limite: Duração (segundos: 30))); tentar { //... var bookToAdd1=Book (); bookToAdd1.id=1; bookToAdd1.title="Coisas desmoronam"; var AddedBook1=await stub.createBook (bookToAdd1); print ("Adicionou um livro:"+ AddedBook1.toString ()); var bookToAdd2=Livro (); bookToAdd2.id=2; bookToAdd2.title="Não é mais fácil"; var AddedBook2=await stub.createBook (bookToAdd2); print ("Adicionou um livro:"+ AddedBook2.toString ()); var allBooks=await stub.getAllBooks (Empty ()); imprimir (allBooks.books.toString ()); var bookToDel=BookId (); bookToDel.id=2; aguarde stub.deleteBook (bookToDel); print ("Livro excluído com ID:"+ 2.toString ()); var allBooks2=aguarda stub.getAllBooks (Vazio ()); imprimir (allBooks2.books); var bookToEdit=Book (); bookToEdit.id=1; bookToEdit.title="Cuidado, irmão da alma"; aguarde stub.editBook (bookToEdit); var bookToGet=BookId (); bookToGet.id=1; var bookGotten=await stub.getBook (bookToGet); print ("Id do livro 1 obtido:"+ bookGotten.toString ()); } catch (e) { imprimir (e); } aguarde channel.shutdown (); } } a Principal() { var cliente=Cliente (); client.main ([]); }
Importamos o pacote grpc.dart
e os arquivos book.pb.dart
e book.pbgrpc.dart
. Criamos uma classe Client
. Temos um BookMethodsClient stub
; o stub
conterá a instância BookMethodsClient
, que é onde podemos chamar os métodos de serviço BookMethods
para invocá-los no servidor.
No método main
, criamos uma instância ClientChannel
e também uma passagem de instância BookMethodsClient
no ClientChannel
instância para seu construtor. BookMethodsClient
usa a instância para obter a configuração-por exemplo, a porta em que o servidor gRPC será alcançado. Em nosso caso, é 50051
e o tempo limite.
Dentro do corpo da instrução try
, chamamos nossos métodos gPRC. Primeiro, criamos um livro com o título “Things Fall Apart” e atribuímos a ele um ID de 1
. Chamamos o método createBook
no stub
, passando a instância Book
bookToAdd1
para o método como arg. Isso chamará o método createBook
no servidor com o objeto addToAdd1
.
A seguir, criamos uma nova instância de livro, “No Longer at Ease,” com o ID 2
e chamamos o método createBook
, passando a instância de livro. Isso invocou remotamente o método createBook
no servidor gRPC e um novo livro foi criado.
Chamamos o método getAllBooks
para obter todos os livros no servidor.
A seguir, configuramos um objeto BooKId
, definindo seu id como 2
. Então, chamamos o método deleteBook
,
passando no objeto BookId
. Isso exclui o livro com id 2
(“No Longer at Ease”) do servidor.
Observe onde editamos um livro. Criamos uma instância de BookId
com um ID definido como 1
e um título definido como “Cuidado, irmão da alma”. Queremos editar o título do livro com o ID 1
para dizer “Cuidado, irmão da alma” em vez de “As coisas caem aos pedaços”. Portanto, chamamos o método editBook
, passando a instância BookId
.
Por último, recuperamos um livro específico usando seu ID. Criamos uma instância de BookId
com seu id
definido como 1
. Isso significa que queremos o livro com o ID 1
, que representa o livro “Cuidado, irmão da alma”. Portanto, chamamos o método getBook
, passando a instância BookId
. O retorno deve ser um objeto Livro
com o título “Cuidado, irmão da alma”.
Depois de tudo isso, o canal é encerrado chamando o método shutdown
em ClientChannel
de sua instância de channel
.
Testando o servidor
Agora é hora de testar tudo. Primeiro, execute o servidor:
➜ dart_grpc dart bin/dart_grpc.dart Servidor ouvindo na porta 50051...
Abra outro terminal e execute o cliente:
➜ dart_grpc dart bin/client.dart Adicionou um livro: id: 1 título: Things Fall Apart Adicionou um livro: id: 2 Título: Não mais à vontade [id: 1 título: Things Fall Apart , id: 2 Título: Não mais à vontade ] Livro excluído com ID: 2 [id: 1 título: Things Fall Apart ] Id do livro 1 obtido: id: 1 título: Cuidado, irmão da alma ➜ dart_grpc
É isso-nosso servidor gRPC está funcionando conforme o esperado!
O código-fonte completo deste exemplo está disponível em GitHub .
Conclusão
Cobrimos muito neste tutorial. Começamos apresentando o gRPC de maneira geral e explicando como ele funciona, desde os buffers de protocolo até o cliente.
A seguir, demonstramos como instalar ferramentas e plug-ins para o compilador de buffer de protocolo. Eles são usados para gerar o código-fonte do DART a partir das definições do proto. Depois disso, percorremos o processo de criação de um serviço gRPC real no Dart, construção de um cliente gRPC e chamada dos métodos do cliente. Por fim, testamos tudo e descobrimos que funciona muito bem.
O gRPC é muito poderoso e há muito mais que você pode descobrir brincando com ele. Os exemplos neste tutorial devem deixar você com uma base sólida.
A postagem Como construir um servidor gRPC em O Dart apareceu primeiro no LogRocket Blog .