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:

  1. servidor hospeda a implementação dos métodos e escuta as solicitações dos clientes
  2. buffer de protocolo contém o formato da mensagem das estruturas de dados e os métodos, incluindo seus argumentos e tipo de retorno
  3. 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 arquivo proto

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 dados Book como arg e retorna um tipo de Book . A implementação deste método criará um novo livro
  • GetAllBooks recebe um tipo Vazio como arg e retorna um tipo Livros . Sua implementação retornará todos os livros
  • O método

  • GetBook aceita um parâmetro de entrada do tipo, BookId e retorna um Book . Sua implementação retornará um livro específico
  • DeleteBook pega um tipo BookId como parâmetro de entrada e retorna um tipo Vazio . Sua implementação excluirá uma entrada de livro da coleção
  • EditBook pega um tipo de Book como arg e retorna um tipo de Book . Sua implementação modificará um livro da coleção

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 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 .