Pular para o conteúdo
Início » Tutorial Completo: Criando uma Unary API gRPC em Java

Tutorial Completo: Criando uma Unary API gRPC em Java

Introdução

No artigo anterior, exploramos o que é o gRPC, destacando suas vantagens em relação ao REST e discutindo a importância do uso do Protobuf para serialização de dados. Neste artigo, vamos implementar uma Unary API gRPC em Java, abordando desde a configuração inicial do projeto até a criação do serviço, servidor e cliente.

A abordagem Unary é a mais simples e comum no gRPC: o cliente faz uma solicitação e recebe uma única resposta, semelhante ao padrão request-response usado em APIs REST. Para isso, utilizaremos o Protobuf, que define a estrutura da API e facilita a comunicação entre cliente e servidor.

O código completo deste tutorial está disponível no GitHub. Você pode conferir o repositório aqui.

Pré-requisitos: Este artigo assume que você possui conhecimentos básicos de Maven, pois utilizaremos este gerenciador de dependências para configurar o projeto. Além disso, recomendamos o uso de uma IDE como o IntelliJ IDEA para facilitar o desenvolvimento e a navegação no código.

Entendendo a Unary API no gRPC

Uma Unary API no gRPC é o tipo mais básico de comunicação entre cliente e servidor, e também o mais comum. Se você já trabalhou com APIs REST, essa abordagem será bastante familiar: é o clássico modelo request-response, onde o cliente faz uma solicitação e o servidor retorna uma resposta correspondente.

Como Funciona?

No caso da Unary API, o fluxo de comunicação é simples:

  1. O cliente envia uma requisição ao servidor.
  2. O servidor processa a solicitação e retorna uma única resposta.

Esse padrão de comunicação é amplamente utilizado porque cobre a maioria dos casos de uso comuns, como busca de dados, criação de recursos, e verificações de status.

Diagrama mostrando o fluxo de comunicação de uma Unary API no gRPC com solicitação e resposta entre cliente e servidor.
Fluxo de Comunicação de uma Unary API: O cliente faz uma solicitação e o servidor retorna uma resposta.

Comparação com REST

Se pensarmos em uma chamada HTTP tradicional, como um método GET ou POST, a Unary API funciona da mesma forma: um único pedido e uma única resposta. No entanto, o uso do gRPC com Protobuf torna essa comunicação mais eficiente, compacta e rápida, devido à serialização binária e ao suporte nativo a múltiplas linguagens.

Definindo a API com Protocol Buffers

Assim como usamos o Swagger ou OpenAPI para definir nossas APIs no REST, no gRPC utilizamos o Protobuf para definir a estrutura da API. O arquivo .proto funciona como um contrato, descrevendo as mensagens e métodos que serão implementados tanto pelo servidor quanto pelo cliente.

Criando Nosso Projeto gRPC em Java

Agora que já entendemos o que é uma Unary API no gRPC, vamos criar nosso primeiro projeto em Java.

O que Vamos Construir?

Vamos criar um projeto Java que utiliza gRPC para implementar uma API Unary chamada CalculadoraService. Esta API terá um método chamado Soma, que recebe dois números inteiros como entrada e retorna o resultado da soma.

Configurando o Maven e Definindo as Dependências

Nesta primeira etapa, vamos criar um projeto Maven e adicionar as dependências necessárias para trabalhar com o gRPC e o Protocol Buffers.

Passo 1: Criando o Projeto Maven

No terminal, execute o seguinte comando para criar o projeto:

mvn archetype:generate -DgroupId=tech.aartedeprogramar -DartifactId=java-grpc -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Este comando cria um projeto básico com a estrutura inicial. Em seguida, você deve importar o projeto na sua IDE de preferência. Recomendo o uso do IntelliJ IDEA, pois facilita o desenvolvimento com ferramentas integradas para trabalhar com Maven e gRPC.

Dica para Usuários do IntelliJ IDEA

Se você estiver utilizando o IntelliJ IDEA, siga estes passos:

  1. Clique em File > Open e selecione o diretório do projeto.
  2. O IntelliJ IDEA irá detectar automaticamente o projeto como Maven e carregar as dependências.
  3. Confirme se o projeto foi importado corretamente e se o Maven está sincronizado.
Passo 2: Adicionando Dependências ao pom.xml

Para adicionar as dependências necessárias, seguiremos as recomendações da documentação oficial do gRPC. Dessa forma, garantimos que estamos utilizando as bibliotecas mais atualizadas e compatíveis para o projeto. Você pode consultar a documentação oficial para detalhes e versões mais recentes neste link.

O arquivo pom.xml ficará assim:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>tech.aartedeprogramar</groupId>
    <artifactId>java-grpc</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>java-grpc</name>
    <url>http://maven.apache.org</url>
    <properties>
        <maven.compiler.source>23</maven.compiler.source>
        <maven.compiler.target>23</maven.compiler.target>
        <java.grpc.version>1.68.1</java.grpc.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>${java.grpc.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${java.grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${java.grpc.version}</version>
        </dependency>
        <dependency> <!-- necessary for Java 9+ -->
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.1</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${java.grpc.version}:exe:${os.detected.classifier}
                    </pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Definindo o Contrato gRPC com Protobuf

Para definir nossa API gRPC, utilizaremos o Protocol Buffers, uma linguagem para especificar a estrutura das mensagens e dos serviços. O arquivo .proto serve como um contrato, definindo claramente as mensagens que serão trocadas entre o cliente e o servidor, além dos métodos que o serviço irá expor.

Onde Colocar o Arquivo .proto?

No projeto Maven, o arquivo deve ser colocado dentro da seguinte pasta:src/main/proto, que é localização padrão esperada pelo plugin protobuf-maven-plugin, que irá compilar o arquivo e gerar o código Java necessário para implementar o serviço gRPC.

Estrutura do Arquivo .proto

Vamos criar o arquivo calculadora.proto com a definição do serviço CalculadoraService e o método soma, que recebe dois números inteiros e retorna o resultado da soma. Este arquivo deve ficar na pasta chamada calculadora dentro da pasta proto.

syntax = "proto3";

package calculadora;

option java_multiple_files = true;

// Definição do serviço CalculadoraService
service CalculadoraService {
  // Método Unary para somar dois números
  rpc Soma (SomaRequest) returns (SomaResponse){};
}

// Mensagem de requisição contendo dois números inteiros
message SomaRequest {
  int32 numero1 = 1;
  int32 numero2 = 2;
}

// Mensagem de resposta contendo o resultado da soma
message SomaResponse {
  int64 resultado = 1;
}
Explicação da Estrutura e Nomenclatura
  • syntax = “proto3”: Define que estamos utilizando a versão 3 do Protobuf, que é a mais recente e recomendada.
  • package calculadora: Define o namespace do serviço, ajudando a evitar conflitos de nomes em projetos que contêm múltiplos serviços.
  • option java_multiple_files = true: Esta opção instrui o compilador Protobuf a gerar cada classe em um arquivo Java separado. Sem esta configuração, o compilador cria uma única classe que encapsula todas as mensagens e o serviço, o que pode tornar o código mais difícil de manter em projetos maiores.
  • service CalculadoraService: O nome do serviço segue a convenção terminando com Service, indicando que se trata de um serviço gRPC.
  • rpc soma: Método Unary que recebe uma mensagem SomaRequest e retorna uma mensagem SomaResponse.
  • Request e Response: Utilizamos os sufixos Request e Response para nomear as mensagens de entrada e saída, respectivamente. Isso facilita a leitura e indica claramente o propósito de cada mensagem.
    • SomaRequest: Representa a mensagem de entrada com os números a serem somados.
    • SomaResponse: Representa a mensagem de saída com o resultado da soma.
  • Os números ao lado de cada campo (por exemplo, numero1 = 1;) são conhecidos como field numbers e são fundamentais para o Protobuf:

    • Identificação Única: Os números identificam de forma única cada campo da mensagem, facilitando a serialização e desserialização dos dados.
    • Compatibilidade e Evolução: Ao adicionar novos campos, utilize números diferentes para evitar conflitos e manter a compatibilidade com versões anteriores.
Tipos de Dados Utilizados no Protobuf

No arquivo .proto, usamos o tipo int32 para representar números inteiros. O Protobuf oferece vários tipos de dados escaláveis, incluindo:

  • int32: Números inteiros de 32 bits, usados para representar a maioria dos inteiros.
  • int64: Números inteiros de 64 bits, usados para representar inteiros maiores.
  • string: Tipo utilizado para representar textos.
  • bool: Tipo booleano para representar valores verdadeiro ou falso.

Esses tipos são escolhidos para garantir eficiência na serialização e transmissão dos dados. Para mais detalhes sobre os tipos de dados disponíveis, você pode consultar a documentação oficial do Protobuf.

Compilando o Arquivo .proto

Após definir o arquivo .proto, o próximo passo é compilar o Protobuf para gerar o código Java. Para isso, execute o comando:

mvn clean compile

Este comando irá gerar automaticamente os arquivos Java dentro da pasta: target/generated-sources/protobuf/java.

Com a opção java_multiple_files = true; ativada, o compilador gerará cada classe em um arquivo separado, tornando o código mais organizado e fácil de manter.

A estrutura dos arquivos gerados será semelhante à seguinte imagem:

Estrutura dos arquivos gerados pelo Protobuf após compilar o arquivo .proto para o serviço CalculadoraService no projeto gRPC em Java.
Estrutura dos arquivos gerados pelo Protobuf, incluindo as classes SomaRequest, SomaResponse e CalculadoraServiceGrpc.

Essa organização facilita a navegação no projeto e permite que cada classe seja utilizada de forma independente.

Implementação Completa do gRPC em Java: Serviço e Servidor

Agora que o código foi gerado com sucesso a partir do arquivo .proto, vamos implementar o serviço CalculadoraService, que define a lógica do método soma. O método soma será sobrescrito para processar as solicitações recebidas e retornar o resultado da soma de dois números.

Implementação do Serviço Calculadora

A primeira parte da implementação é criar o serviço que define a lógica do método soma, responsável por somar dois números inteiros enviados pelo cliente.

package tech.aartedeprogramar.services;

import calculadora.CalculadoraServiceGrpc;
import calculadora.SomaRequest;
import calculadora.SomaResponse;
import io.grpc.stub.StreamObserver;

public class CalculadoraServiceImpl extends CalculadoraServiceGrpc.CalculadoraServiceImplBase {

    @Override
    public void soma(SomaRequest request, StreamObserver<SomaResponse> responseObserver) {
        int numero1 = request.getNumero1();
        int numero2 = request.getNumero2();
        int soma = numero1 + numero2;

        SomaResponse response = SomaResponse.newBuilder()
                .setResultado(soma)
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

A classe CalculadoraServiceImpl estende a base gerada pelo Protobuf, chamada CalculadoraServiceGrpc.CalculadoraServiceImplBase. Nesta classe, precisamos sobrescrever o método soma, que foi definido no arquivo .proto.

Funcionamento do Método soma

O método soma é do tipo void, pois não retorna o resultado diretamente. Em vez disso, utilizamos o StreamObserver para enviar a resposta ao cliente. O StreamObserver possui três métodos principais:

  1. onNext(response): Envia a resposta para o cliente. Neste caso, é utilizado para enviar o resultado da soma.
  2. onCompleted(): Finaliza a chamada RPC, indicando que a resposta foi enviada com sucesso.
  3. onError(Throwable t): Utilizado para tratar erros e enviar uma mensagem de erro ao cliente, caso ocorra algum problema.
Por que Usamos StreamObserver?

O gRPC é projetado para ser assíncrono e não bloqueante, o que significa que o servidor não retorna diretamente o resultado como em métodos síncronos. Em vez disso, ele utiliza o StreamObserver para enviar a resposta quando estiver pronta. Isso permite ao servidor lidar com múltiplas solicitações simultaneamente de maneira eficiente.

Criação do Servidor gRPC

Com o serviço CalculadoraServiceImpl implementado, estamos prontos para criar o servidor gRPC. O servidor será responsável por hospedar o serviço e escutar as solicitações dos clientes. Nesta implementação, vamos configurar o servidor para iniciar, escutar na porta 50051. Além disso, para garantir que o servidor seja encerrado corretamente, incluímos um Shutdown Hook.

Código do Servidor
package tech.aartedeprogramar.server;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import tech.aartedeprogramar.services.CalculadoraServiceImpl;

public class CalculadoraServer {

    public static void main(String[] args) throws Exception {
        // Criando o servidor gRPC na porta 50051
        Server server = ServerBuilder.forPort(50051)
                .addService(new CalculadoraServiceImpl())
                .build();
        // Adicionando o Shutdown Hook para encerramento controlado
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Encerrando servidor gRPC.");
            server.shutdown();
            System.out.println("Serviddor gRPC encerrado com sucesso.");
        }));

        // Iniciando o servidor
        System.out.println("Servidor gRPC iniciado na porta 50051.");
        server.start();
        server.awaitTermination();
    }
}
Explicação do Código
  • ServerBuilder.forPort(50051): Configura o servidor para escutar na porta 50051, que é uma porta comum para serviços gRPC.
  • addService: Adiciona a instância do serviço CalculadoraServiceImpl ao servidor.
  • start(): Inicia o servidor gRPC, permitindo que ele comece a escutar as solicitações dos clientes.
  • awaitTermination(): Mantém o servidor em execução até que seja interrompido manualmente.
Tratamento de Encerramento com Shutdown Hook

O Shutdown Hook permite que o servidor gRPC seja encerrado de forma controlada quando o programa é interrompido (por exemplo, pressionando Ctrl + C no terminal). Isso garante que todos os recursos sejam liberados corretamente.

Implementando o Cliente gRPC para o Serviço Calculadora

Com o servidor gRPC configurado e em execução, agora vamos implementar o cliente que irá se conectar ao servidor e enviar solicitações para o método soma. O cliente será responsável por enviar dois números inteiros e receber o resultado da soma como resposta.

Estrutura do Cliente

Vamos criar uma classe chamada CalculadoraClient que:

  1. Estabelece a conexão com o servidor gRPC.
  2. Envia uma solicitação para o método soma com dois números inteiros.
  3. Recebe a resposta do servidor e exibe o resultado.
Código do Cliente
package tech.aartedeprogramar.client;

import calculadora.CalculadoraServiceGrpc;
import calculadora.SomaRequest;
import calculadora.SomaResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class CalculadoraCliente {
    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 50051)
                .usePlaintext()
                .build();

        CalculadoraServiceGrpc.CalculadoraServiceBlockingStub cliente = CalculadoraServiceGrpc.newBlockingStub(managedChannel);

        SomaRequest somaRequest = SomaRequest.newBuilder()
                .setNumero1(10)
                .setNumero2(5)
                .build();

        SomaResponse response = cliente.soma(somaRequest);

        System.out.println(response.getResultado());
        managedChannel.shutdown();
    }
}
Explicação do Código
  • ManagedChannel:
    • Cria o canal de comunicação com o servidor gRPC na porta 50051 do host local (localhost).
    • O método usePlaintext() configura o canal para usar comunicação sem criptografia, adequado para testes locais.
  • CalculadoraServiceBlockingStub:
    • É o stub gerado pelo Protobuf que permite fazer chamadas síncronas ao servidor.
    • O stub invoca o método remoto soma no servidor, enviando a solicitação e recebendo a resposta.
  • SomaRequest:
    • Representa a mensagem de requisição contendo os dois números inteiros (numero1 e numero2) a serem somados.
  • SomaResponse:
    • Recebe o resultado da operação de soma executada no servidor.
    • O método getResultado() retorna o resultado da soma.
  • Chamada Remota:
    • O método cliente.soma(somaRequest) é uma chamada remota (RPC) que executa o método soma no servidor gRPC.
    • O servidor processa a solicitação e retorna a resposta ao cliente.
  • Encerramento do Canal:
    • managedChannel.shutdown() fecha o canal de comunicação, liberando os recursos utilizados.

Executando o Cliente

Para executar o cliente, siga os passos abaixo:

  1. Inicie o servidor gRPC executando a classe CalculadoraServer.
  2. Em seguida, execute a classe CalculadoraCliente.

Se tudo estiver configurado corretamente, você verá a seguinte saída no terminal: Resultado da soma: 15

Usando o Postman para Requisições gRPC

Além de testar a API com o cliente Java, você também pode utilizar o Postman, que também oferece suporte para chamadas gRPC. 

  1. Abra o Postman e crie uma nova requisição gRPC.
  2. Em seguida, no campo de URL, insira: localhost:50051.
  3. Importe o arquivocalculadora.proto.Dessa forma, o Postman reconhecerá automaticamente os métodos e as mensagens definidas no arquivo.
  4. Escolha o método soma e adicione o payload JSON conforme o exemplo abaixo e clique em “Invoke”.
{
  "numero1": 10,
  "numero2": 5
}

Conclusão

Parabéns! Você completou com sucesso o nosso tutorial de implementação de uma API Unary utilizando gRPC em Java. Neste artigo, abordamos desde as configurações iniciais do projeto até a criação completa do serviço, servidor e cliente. Ao seguir o passo a passo, você aprendeu a:

  • Definir uma API gRPC com Protobuf e entender a estrutura do arquivo .proto.
  • Configurar o projeto em Java usando Maven e compilar o Protobuf.
  • Implementar o serviço gRPC (CalculadoraServiceImpl) e criar o servidor (CalculadoraServer).
  • Desenvolver o cliente gRPC (CalculadoraCliente) para realizar chamadas remotas e testar o serviço.

Este é apenas o começo da sua jornada com gRPC. No próximo artigo, vamos explorar APIs de Streaming, que permitem a troca de múltiplas mensagens entre cliente e servidor, ampliando as possibilidades de comunicação.

Se você quiser ver o código completo ou fazer ajustes, acesse o repositório no GitHub: Unary API gRPC em Java – Repositório GitHub

Gostou do artigo? 💬 Deixe seu comentário abaixo com dúvidas, sugestões ou ideias para futuros conteúdos. E se você acha que este artigo pode ajudar alguém, compartilhe com seus amigos e colegas. 📣 Fique atento ao próximo conteúdo sobre APIs de Streaming no gRPC!

Referências

  • gRPC: Introdução e Documentação Oficial – Fonte oficial do gRPC, com detalhes sobre suas funcionalidades, arquitetura e uso.
  • LivrogRPC: Up and Running: Building High-Performance Microservices with gRPC, disponível em O’Reilly. Esse livro fornece uma visão aprofundada do gRPC em Java, ideal para quem deseja expandir seus conhecimentos.
  •  

1 comentário em “Tutorial Completo: Criando uma Unary API gRPC em Java”

  1. Pingback: Server Streaming API no gRPC com Java: Retorne Dados em Tempo Real - A Arte de Programar

Não é possível comentar.

Marcações: