DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. gRPC on the Client Side

gRPC on the Client Side

Here, learn about gRPC and how it benefits inter-service communication, and develop a simple gRPC service using Spring Boot and grpc-server-spring-boot-starter.

Nicolas Fränkel user avatar by
Nicolas Fränkel
CORE ·
Mar. 18, 23 · Tutorial
Like (3)
Save
Tweet
Share
3.95K Views

Join the DZone community and get the full member experience.

Join For Free

Most inter-systems communication components that use REST serialize their payload in JSON. As of now, JSON lacks a widely-used schema validation standard: JSON Schema is not widespread. Standard schema validation allows delegating the validation to a third-party library and being done with it. Without one, we must fall back to manual validation in the code. Worse, we must keep the validation code in sync with the schema.

XML has schema validation out-of-the-box: an XML document can declare a grammar that it must conform to. SOAP, being based on XML, benefits from it, too.

Other serialization alternatives have a schema validation option: e.g., Avro, Kryo, and Protocol Buffers. Interestingly enough, gRPC uses Protobuf to offer RPC across distributed components:

gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

- Why gRPC?

Moreover, Protocol is a binary serialization mechanism, saving a lot of bandwidth. Thus, gRPC is an excellent option for inter-system communication. But if all your components talk gRPC, how can simple clients call them? In this post, we will build a gRPC service and show how to call it from cURL.

A Simple gRPC Service

The gRPC documentation is exhaustive, so here's a summary:

  • gRPC is a Remote Procedure Call framework.
  • It works across a wide range of languages.
  • It relies on Protocol Buffers:
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

- Protocol Buffers
  • It's part of the CNCF portfolio and is currently in the incubation stage.

Let's set up our gRPC service. We will use Java, Kotlin, Spring Boot, and a dedicated gRPC Spring Boot integration project. The project structure holds two projects: one for the model and one for the code. Let's start with the model project.

I didn't want something complicated. Reusing a simple example is enough: the request sends a string, and the response prefixes it with Hello. We design this model in a dedicated Protobuf schema file:

ProtoBuf
 
syntax = "proto3";                                        //1

package ch.frankel.blog.grpc.model;                       //2

option java_multiple_files = true;                        //3
option java_package = "ch.frankel.blog.grpc.model";       //3
option java_outer_classname = "HelloProtos";              //3

service HelloService {                                    //4
    rpc SayHello (HelloRequest) returns (HelloResponse) {
    }
}

message HelloRequest {                                    //5
    string name = 1;                                      //6
}

message HelloResponse {                                   //7
    string message = 1;                                   //6
}


  1. Protobuf definition version
  2. Package
  3. Java-specific configuration
  4. Service definition
  5. Request definition
  6. Field definition: First comes the type, then the name, and finally, the order.
  7. Response definition

We shall use Maven to generate the Java boilerplate code:

XML
 
<project>
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-stub</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-protobuf</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>jakarta.annotation</groupId>              <!--1-->
      <artifactId>jakarta.annotation-api</artifactId>
      <version>1.3.5</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>                 <!--2-->
        <artifactId>os-maven-plugin</artifactId>
        <version>1.7.1</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>    <!--3-->
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>${protobuf-plugin.version}</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-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>


  1. Compile-time dependencies
  2. Sniff information about the Operating System; used in the next plugin
  3. Generate Java code from the proto file. 

After compilation, the structure should look something like the following:

Structure after compilation

We can package the classes in a JAR and use it in a web app project. The latter is in Kotlin, but only because it's my favorite JVM language.

We only need a specific Spring Boot starter dependency to integrate gRPC endpoints with Spring Boot:

XML
 
<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>2.14.0.RELEASE</version>
</dependency>


Here's the significant bit:

Kotlin
 
@GrpcService                                                        //1
class HelloService : HelloServiceImplBase() {                       //2
  override fun sayHello(
      request: HelloRequest,                                        //2
      observer: StreamObserver<HelloResponse>                       //3
  ) {
    with(observer) {
      val reply = HelloResponse.newBuilder()                        //2
                               .setMessage("Hello ${request.name}") //4
                               .build()
      onNext(reply)                                                 //5
      onCompleted()                                                 //5
    }
  }
}


  1. The grpc-server-spring-boot-starter detects the annotation and works its magic.
  2. Reference classes generated in the above project
  3. The method signature allows a StreamObserver parameter. The class comes from grpc-stub.jar. 
  4. Get the request and prefix it to build the response message.
  5. Play the events. 

We can now start the web app with ./mvnw spring-boot:run.

Testing the gRPC Service

The whole idea behind the post is that accessing the gRPC service with regular tools is impossible. To test, we need a dedicated tool nonetheless. I found grpcurl. Let's install it and use it to list available services:

Shell
 
grpcurl --plaintext localhost:9090 list   #1-2


  1. List all available gRPC services without TLS verification. 
  2. To avoid clashes between gRPC and other channels, e.g., REST, Spring Boot uses another port.
Plain Text
 
ch.frankel.blog.grpc.model.HelloService   #1
grpc.health.v1.Health                     #2
grpc.reflection.v1alpha.ServerReflection  #2


  1. The gRPC service we defined
  2. Two additional services provided by the custom starter

We can also dive into the structure of the service:

Shell
 
grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
Java
 
service HelloService {
  rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}


Finally, we can call the service with data:

Shell
 
grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
JSON
 
{
  "message": "Hello John"
}


Accessing the gRPC Service With Regular Tools

Imagine that we have a regular JavaScript client-side application that needs to access the gRPC service. What would be the alternatives?

The general approach is through grpc-web:

A JavaScript implementation of gRPC for browser clients. For more information, including a quick start, see the gRPC-web documentation.

gRPC-web clients connect to gRPC services via a special proxy; by default, gRPC-web uses Envoy.

In the future, we expect gRPC-web to be supported in language-specific web frameworks for languages such as Python, Java, and Node. For details, see the roadmap.

- grpc-web

The description states a single limitation: it works only for JavaScript (as of now). However, there's another one. It's pretty intrusive. You need to get the proto file, generate boilerplate code, and make your code call it. You must do it for every client type. Worse, if the proto file changes, you need to regenerate the client code in each of them.

An alternative exists, though, if you're using an API Gateway. I'll describe how to do it with Apache APISIX, but perhaps other gateways can do the same. grpc-transcode is a plugin that allows transcoding REST calls to gRPC and back again.

The first step is to register the proto file in Apache APISIX:

Shell
 
curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ \"content\": \"$(sed 's/"/\\"/g' ../model/src/main/proto/model.proto)\" }"


The second step is to create a route with the above plugin:

Shell
 
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",                           #1
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",                                       #2
      "service": "ch.frankel.blog.grpc.model.HelloService",  #3
      "method": "SayHello"                                   #4
    }
  },
  "upstream": {
    "scheme": "grpc",
    "nodes": {
      "server:9090": 1
    }
  }
}'


  1. Define a granular route. 
  2. Reference the proto file defined in the previous command. 
  3. gRPC service
  4. gRPC method

At this point, any client can make an HTTP request to the defined endpoint. Apache APISIX will transcode the call to gRPC, forward it to the defined service, get the response, and transcode it again.

Shell
 
curl localhost:9080/helloservice/sayhello?name=John
JSON
 
{"message":"Hello John"}


Compared to grpc-web, the API Gateway approach allows sharing the proto file with a single component: the Gateway itself.

Benefits of Transcoding

At this point, we can leverage the capabilities of the API Gateway. Imagine we want a default value if no name is passed, e.g., World. Developers would happily set it in the code, but any change to the value would require a complete build and deployment. Changes can be nearly-instant if we put the default value in the Gateway's routes processing chain. Let's change our route accordingly:

Shell
 
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",
  "plugins": {
    "grpc-transcode": {
      ...
    },
    "serverless-pre-function": {                    #1
      "phase": "rewrite",                           #2
      "functions" : [
        "return function(conf, ctx)                 #3
          local core = require(\"apisix.core\")
          if not ngx.var.arg_name then
            local uri_args = core.request.get_uri_args(ctx)
            uri_args.name = \"World\"
            ngx.req.set_uri_args(uri_args)
          end
        end"
      ]
    }
  },
  "upstream": {
      ...
  }
}'


  1. Generic all-purpose plugin when none fits
  2. Rewrite the request.
  3. Magic Lua code that does the trick

Now, we can execute the request with an empty argument and get the expected result:

Shell
 
curl localhost:9080/helloservice/sayhello?name
JSON
 
{"message":"Hello World"}


Conclusion

In this post, we have briefly described gRPC and how it benefits inter-service communication. We developed a simple gRPC service using Spring Boot and grpc-server-spring-boot-starter. It comes at a cost, though: regular clients cannot access the service. We had to resort to grpcurl to test it. The same goes for clients based on JavaScript - or the browser.

To bypass this limitation, we can leverage an API Gateway. I demoed how to configure Apache APISIX with the grpc-transcode plugin to achieve the desired result.

The complete source code for this post can be found on GitHub.

To Go Further

  • os-maven-plugin
  • Maven Protocol Buffers Plugin
  • gRPC-Spring-Boot-Starter
API CURL gRPC Protocol Buffers Spring Boot

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Building a RESTful API With AWS Lambda and Express
  • [DZone Survey] Share Your Expertise and Take our 2023 Web, Mobile, and Low-Code Apps Survey
  • Spring Cloud
  • Cucumber.js Tutorial With Examples For Selenium JavaScript

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: