Materiais de apoio à aula
Exemplos:
- JUnit

- Para experimentar o código: fazer Clone or Download e depois seguir as instruções do README
- gRPC example with errors and tests

- Para experimentar o código: fazer Clone or Download e depois seguir as instruções do README
Exercício
Implementação da operação ctrlPing no projeto (P1)
O objetivo do exercício é construir e testar uma primeira operação remota no projeto.
Para este fim, vai-se:
-
Definir o ctrl_ping no contrato, implementar no servidor e testar manualmente com o cliente;
-
Enviar informação de erro do servidor para o cliente;
-
Testar a operação do servidor a partir do cliente com testes de integração JUnit 5.
Vamos então começar!
O ponto de partida é o código base que está no repositório GitHub de cada grupo, que deve ser obtido com o comando git clone.
Antes de começar, e para prevenir conflitos nos nomes dos módulos Maven, deve alterar os pom.xml e README.md, substituindo todas as referências a CXX pelo identificador do grupo.
Por exemplo, o grupo A22 corresponde ao grupo 22 sediado no campus Alameda; já o grupo T07 corresponde ao grupo 7 sediado no Taguspark.
-
Definir, implementar e experimentar a operação remota ctrl_ping
-
Existem três módulos: contract, server e client.
Vamos começar pelo contrato:
-
Aceder ao ficheiro .proto.
Este ficheiro define as estruturas de dados e as operações remotas.
-
Cada operação necessita de uma mensagem de pedido e de uma mensagem de resposta.
-
Definir a mensagem do pedido, PingRequest, que recebe texto simples.
-
Definir a mensagem do resposta, PingResponse, que devolve também texto simples.
-
Definir a operação rpc com o nome ctrl_ping, com os tipos de pedido e resposta indicados.
-
Vamos gerar código Java a partir dos protocol buffers.
O Maven está configurado com plug-ins para chamar a ferramenta protoc (protocol buffers compiler)
-
Executar: mvn install
-
Se tudo correr bem, as definições protobuf foram convertidas para classes Java, que foram compiladas e instaladas no repositório local do Maven.
-
Para consultar o código gerado, faça refresh
(right-click, Maven, Update Project, Force Update of Snapshots/Releases, OK)
no Eclipse e depois
consulte os ficheiros na pasta:
target/generated-sources/protobuf.
Há classes que representam os tipos de dados das mensagens e há classes de suporte ao servidor e ao cliente do RPC.
-
Vamos agora concretizar o servidor:
-
As classes de domínio da aplicação, onde são definidas as entidades e os comportamentos, ficam no pacote domain.
-
Identifique o Domain Root e as restantes entidades representadas nas classes.
-
Veja os mecanismos de sincronização que são utilizados para garantir que as classes podem ser chamadas corretamente por múltiplas tarefas (threads).
Para o ctrl_ping não deve ser necessário usar o domínio, mas para as operações principais será necessário.
-
Com base nas classes geradas pelo protoc e, por analogia com outros exemplos de servidores gRPC já vistos nas aulas, crie a classe ...ServerImpl e adicione o método de implementação do ctrl_ping:
public void ctrlPing(PingRequest request,
StreamObserver<PingResponse> responseObserver) {
String input = request.getInputText();
String output = "Hello " + input + "!";
PingResponse response = PingResponse.newBuilder().
setOutputText(output).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
-
Ajuste a classe ServerApp para instanciar o servidor e direcionar os pedidos para a implementação.
-
Para compilar e testar, fazer: mvn compile exec:java
-
Vamos agora concretizar o cliente:
-
O ...Frontend vai envolver o channel e o stub gerado pelo protoc.
Todos os pedidos para o servidor vão depois usar este objeto ...Frontend, o que permitirá mais tarde acrescentar funcionalidades.
-
O objeto ...ClientApp deverá criar um frontend e fazer uma chamada à operação remota:
ServerFrontend frontend = new ServerFrontend(host, port);
...
PingRequest request = PingRequest.newBuilder().setInputText("friend").build();
PingResponse response = frontend.ctrlPing(request);
System.out.println(response);
-
Para compilar e testar:
mvn compile exec:java
Se tudo correr bem, deverá aparecer a mensagem: Hello friend!
-
Enviar informação de erro do servidor para o cliente
-
Vamos adicionar um retorno de erro ao servidor caso a mensagem do pedido seja vazia.
Importar a definição de um estado de erro para argumentos inválidos:
import static io.grpc.Status.INVALID_ARGUMENT;
...
-
Verificar se o argumento é vazio e devolver o erro em caso afirmativo.
...
if (inputText == null || inputText.isBlank()) {
responseObserver.onError(INVALID_ARGUMENT
.withDescription("Input cannot be empty!").asRuntimeException());
}
...
-
Do lado do cliente, vamos apanhar uma exceção e imprimir a mensagem de erro:
try {
PingRequest request = PingRequest.newBuilder().setInputText("").build();
PingResponse response = frontend.ctrlPing(request);
} catch (StatusRuntimeException e) {
System.out.println("Caught exception with description: " +
e.getStatus().getDescription());
}
-
Testar a operação do servidor a partir do cliente
-
Vamos agora criar um teste de integração para verificar este comportamento do servidor.
Criar a classe PingIT na pasta src/test/java/, seguindo a convenção de uma pasta por cada pacote (package) Java.
-
Primeiro vamos criar um teste para o caso normal:
@Test
public void pingOKTest() {
PingRequest request = PingRequest.newBuilder().setInputText("friend").build();
PingResponse response = frontend.ctrlPing(request);
assertEquals("Hello friend!", response.getOutputText());
}
-
Para correr os testes, lançar o servidor primeiro, e depois fazer: mvn verify
O resultado deverá ser bem sucedido para todos os testes.
-
Vamos de seguida criar um segundo teste, para o caso de erro:
@Test
public void emptyPingTest() {
PingRequest request = PingRequest.newBuilder().setInputText("").build();
assertEquals(
INVALID_ARGUMENT.getCode(),
assertThrows(
StatusRuntimeException.class, () -> frontend.ctrlPing(request))
.getStatus()
.getCode());
}
-
Correr novamente os testes com mvn verify.
O resultado deverá ser, novamente, bem sucedido para todos os testes.
Repare que este segundo teste tem sucesso se houver exceção, porque é isso que é esperado.
Próximos passos
-
Adicionar as restantes operações, uma a uma.
Primeiro as de controlo, ctrl_clear, ctrl_init, e depois as principais.
-
Testar todas as operações desenvolvidas.
-
Concretizar os restantes requisitos do projeto.