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:
-
servidorhospeda a implementação dos métodos e escuta as solicitações dos clientes -
buffer de protocolocontém o formato da mensagem das estruturas de dados e os métodos, incluindo seus argumentos e tipo de retorno -
clientechama 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 .
-
CreateBookpega um tipo de dadosBookcomo arg e retorna um tipo deBook. A implementação deste método criará um novo livro -
GetAllBooksrecebe um tipoVaziocomo arg e retorna um tipoLivros. Sua implementação retornará todos os livros -
GetBookaceita um parâmetro de entrada do tipo,BookIde retorna umBook. Sua implementação retornará um livro específico -
DeleteBookpega um tipoBookIdcomo parâmetro de entrada e retorna um tipoVazio. Sua implementação excluirá uma entrada de livro da coleção -
EditBookpega um tipo deBookcomo 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.dartbook.pbenum.dartbook.pbgrpc.dartbook.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 Futuro createBook (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; Futuro main (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 .