3. gRPC: conceitos base
Objetivos desta aula:
- Distribuir uma aplicação originalmente centralizada usando o gRPC
- Descrever, em detalhe, os componentes do sistema gRPC
Materiais de apoio à aula
- Introdução ao gRPC (slides)
- Tutorial de gRPC para Java
- Documentação de Protocol Buffers
- API de gRPC para Java
Antes de começar a programar com gRPC
Comece por folhear os slides de introdução ao gRPC que fornecemos acima. Como são sucintos, é natural que suscitem algumas dúvidas. O exercício seguinte ajudará a esclarecê-las, assim como os materiais de apoio listados acima. E, claro, pode sempre esclarecer qualquer questão contactando os docentes (em aula, horário de dúvidas, ou moodle). Exercício a resolver até ao fim da aula
Neste exercício iremos transformar uma implementação do Jogo do Galo (Tic Tac Toe) numa aplicação distribuída utilizando o gRPC.
I. Começar por uma implementação local do Jogo do Galo/Tic Tac Toe.
- Faça Clone ou Download do código fonte do jogo
- Analise o código do jogo de forma a compreender a implementação.
- Compile e execute o código com o comando:
mvn compile exec:java
II. Estudar a tecnologia gRPC.
Pretende-se que a nova versão da aplicação seja dividida em dois processos: servidor e cliente, através do gRPC. Para tal, vamos começar por estudar a tecnologia gRPC.
- Faça Clone ou Download do código fonte do exemplo gRPC
- Veja como a aplicação está estruturada em três módulos:
contract
,server
eclient
. - Cada módulo tem um POM próprio.
Nos passos seguintes, vamos compilar e executar o exemplo seguindo as instruções README.md
de cada módulo.
- Comece pelo módulo contract, executando o comando: mvn install. Este comando vai passar pela etapa generate-sources, que vai invocar o protoc, o compilador de protocol buffers que vai gerar código Java para lidar com os tipos de dados descritos no ficheiro .proto.
- Familiarize-se com o código e responda às seguintes questões:
- Onde estão definidas as mensagens trocadas entre o cliente e o servidor?
- Onde estão definidos os procedimentos remotos no servidor?
- Onde estão os ficheiros gerados pelo compilador de Protocol Buffers?
- Onde são feitas as invocações remotas no cliente?
- As invocações remotas são síncronas (bloqueantes) ou assíncronas?
- Abra uma consola, entre na diretoria do módulo
server
e corra o servidor:mvn compile exec:java
- Abra uma outra consola, entre na diretoria do módulo
client
e execute o cliente:mvn compile exec:java
- Depois de ver o
Hello World
a funcionar corretamente no seu computador, avance para o passo seguinte.
III. Transformar o Jogo do Galo numa aplicação cliente-servidor com gRPC
A aplicação distribuída será organizada em três módulos. À semelhança do exemplo, o contrato irá definir a interface remota, com detalhes sobre as mensagens a trocar. O servidor irá manter o estado do jogo (tabuleiro). O cliente irá ter a interface utilizador na consola.
-
Faça Clone ou Download do código inicial do exercício
-
Baseando-se no módulo contract da aplicação de exemplo, modifique o ficheiro .proto com as definições necessárias para as chamadas remotas de procedimentos
currentBoard
,play
echeckWinner
.- Sugestão: consulte a documentação dos Protocol Buffers.
- Declare todas as mensagens de pedido e resposta para cada procedimento do jogo. Note que algumas mensagens podem ser vazias, mas devem ser declaradas na mesma.
- Cada campo deve ter uma etiqueta numérica única.
- Complete o serviço TTT com as definições dos procedimentos que definiu (assim como as mensagens que definiu).
- Instale o módulo com o comando
mvn install
. - Analise o código Java gerado na pasta
target/generated-sources/
.- Onde estão definidas as mensagens?
- E os procedimentos?
- Instale o módulo com o comando
-
Baseando-se no módulo server da aplicação de exemplo, modifique o código inicial do módulo
server
.- Confirme que o módulo contract é uma dependência do projeto.
- Modifique a classe
TTTServiceImpl
de forma a implementar os procedimentos remotos declarados no contrato, utilizando a classeTTTGame
(que implementa a lógica do jogo) definida no código base. A classe de implementação do serviço estende a classe do serviço definido no contrato e faz override dos procedimentos declarados no contrato.
Exemplo de um método:
public class TTTServiceImpl extends TTTGrpc.TTTImplBase { private TTTGame ttt = new TTTGame(); @Override public void currentBoard(CurrentBoardRequest request, StreamObserver<CurrentBoardResponse> responseObserver) { CurrentBoardResponse response = CurrentBoardResponse.newBuilder().setBoard(ttt.toString()).build(); responseObserver.onNext(response); responseObserver.onCompleted(); }
Relembre a mensagem definida no contrato:
message CurrentBoardRequest { // No arguments for this request. } message CurrentBoardResponse { string board = 1; }
-
Confirme que a classe
TTTServer
inicia um servidor numa porta que recebe como argumento, instanciando a classe de implementação do serviço. Tenha em conta que o acesso a variáveis partilhadas tem de ser sincronizado.- Porque é que esta sincronização é necessária?
- Onde é que há possibilidade de concorrência?
-
Lance o servidor:
mvn compile exec:java
-
Por fim, complete o código do módulo
client
.- Confirme que o módulo
contract
é uma dependência do projeto. Confirme que a classeTTTClient
instancia um stub do serviçoTTT
(através de um endereço e porta recebidos como argumentos). - Adicione as chamadas remotas aos procedimentos
play
echeckWinner
que estão em falta.
Exemplo de chamada local:
winner = ttt.checkWinner();
Exemplo de chamada remota correspondente:
winner = stub.checkWinner(CheckWinnerRequest.getDefaultInstance()).getResult();
- Confirme que o módulo
-
Experimente jogar remotamente através do cliente construído:
mvn compile exec:java
Já resolveram?
Podem conferir a nossa proposta de resolução.
Nota: esta solução resolve também o exercício do próximo guião.