Chapter 2. Getting Started with gRPC

Enough with the theory on gRPC; let’s apply what you learned in Chapter 1 to build a real-world gRPC application from the ground up. In this chapter, you will use both Go and Java to build a simple gRPC service and a client application that invokes the service you developed. In the process you’ll learn about specifying a gRPC service definition using protocol buffers, generating a server skeleton and client stub, implementing a service’s business logic, running a gRPC server with the service you implemented, and invoking the service through the gRPC client application.

Let’s use the same online retail system from Chapter 1, where we need to build a service that is responsible for managing the products of a retail store. The service can be remotely accessed and the consumers of that service can add new products to the system and also retrieve product details from the system by providing the product ID. We’ll model this service and consumer using gRPC. You may pick the programming language of your choice to implement this, but in this chapter, we will use both the Go and Java languages to implement this sample.

Note

You can try out both the Go and Java implementations of the sample in the source code repository for this book.

In Figure 2-1, we illustrate the client–server communication patterns of the ProductInfo service for each method invocation. The server hosts a gRPC service that offers two remote methods: addProduct(product) and getProduct(productId). The client can invoke either of those remote methods.

client-server interaction of Product Info service
Figure 2-1. Client–server interaction of ProductInfo service

Let’s start building this sample by creating the service definition of the ProductInfo gRPC service.

Creating the Service Definition

As you learned in Chapter 1, when you develop a gRPC application, the first thing you do is define the service interface, which contains the methods that allow consumers to call remotely, the method parameters and message formats to use when invoking those methods, and so on. All these service definitions are recorded as a protocol buffer’s definition, which is the interface definition language (IDL) used in gRPC.

Note

We will further dive into service definition techniques for different messaging patterns in Chapter 3. We will also cover the details of protocol buffers and gRPC implementation details in Chapter 4.

Once you identify the business capabilities of the service, you can define the service interface to fulfill the business need. In our sample, we can identify two remote methods (addProduct(product) and getProduct(productId)) in the ProductInfo service and two message types (Product and ProductID) that both methods accept and return.

The next step is to specify these service definitions as a protocol buffer definition. With protocol buffers, we can define services and message types. A service consists of its methods and each method is defined by its type, input, and output parameters. The message consists of its fields and each field is defined by its type and a unique index value. Let’s dive into the details of defining message structures.

Defining Messages

The message is the data structure that is exchanged between client and service. As you can see in Figure 2-1, our ProductInfo use case has two message types. One is the product information (Product), which is required when adding a new product to the system and is returned when retrieving a particular product. The other is a unique identification (ProductID) of the product, which is required when retrieving a particular product from the system and is returned when adding a new product:

ProductID

ProductID is a unique identifier of the product that can be a string value. We can either define our own message type that contains a string field or use the well-known message type google.protobuf.StringValue, provided by the protocol buffer library. In this example, we are going to define our own message type that contains a string field. The ProductID message type definition is shown in Example 2-1.

Example 2-1. Protocol Buffer definition of ProductID message type.
message ProductID {
   string value = 1;
}
Product

Product is a custom message type that represents the data that should exist in a product in our online retail application. It can have a set of fields that represent the data associated with each product. Suppose the Product message type has the following fields:

ID

Unique identifier of the product

Name

Product name

Description

Product description

Price

Product price

Then we can define our custom message type using a protocol buffer as shown in Example 2-2.

Example 2-2. Protocol buffer definition of Product message type
message Product {
   string id = 1;
   string name = 2;
   string description = 3;
   float price = 4;
}

Here the number assigned to each message field is used to uniquely identify the field in the message. So, we can’t use the same number in two different fields in the same message definition. We will further dive into the details of the message definition techniques of protocol buffers and explain why we need to provide a unique number for each field in Chapter 4. For now, you can think of it as a rule when defining a protocol buffer message.

Note

The protobuf library provides a set of protobuf message types for well-known types. So we can reuse them instead of defining such types again in our service definition. You can get more details about these well-known types in the protocol buffers documentation.

Since we have completed defining message types for the ProductInfo service, we can move on to the service interface definition.

Defining Services

A service is a collection of remote methods that are exposed to a client. In our sample, the ProductInfo service has two remote methods: addProduct(product) and getProduct(productId). According to the protocol buffer rule, we can only have one input parameter in a remote method and it can return only one value. If we need to pass multiple values to the method like in the addProduct method, we need to define a message type and group all the values as we have done in the Product message type:

addProduct

Creates a new Product in the system. It requires the details of the product as input and returns the product identification number of the newly added product, if the action completed successfully. Example 2-3 shows the definition of the addProduct method.

Example 2-3. Protocol buffer definition of addProduct method
rpc addProduct(Product) returns (google.protobuf.StringValue);
getProduct

Retrieves product information. It requires the ProductID as input and returns Product details if a particular product exists in the system. Example 2-4 shows the definition of the getProduct method.

Example 2-4. Protocol buffer definition of getProduct method
rpc getProduct(google.protobuf.StringValue) returns (Product);

Combining all messages and services, we now have a complete protocol buffer definition for our ProductInfo use case, as shown in Example 2-5.

Example 2-5. gRPC service definition of ProductInfo service using protocol buffers
syntax = "proto3"; 1
package ecommerce; 2

service ProductInfo { 3
    rpc addProduct(Product) returns (ProductID); 4
    rpc getProduct(ProductID) returns (Product); 5
}

message Product { 6
    string id = 1; 7
    string name = 2;
    string description = 3;
}

message ProductID { 8
    string value = 1;
}
1

The service definition begins with specifying the protocol buffer version (proto3) that we use.

2

Package names are used to prevent name clashes between protocol message types and also will be used to generate code.

3

Definition of the service interface of the service.

4

Remote method to add a product that returns the product ID as the response.

5

Remote method to get a product based on the product ID.

6

Definition of the message format/type of Product.

7

Field (name-value pair) that holds the product ID with unique field numbers that are used to identify your fields in the message binary format.

8

Definition of the message format/type of ProductID.

In the protocol buffer definition, we can specify a package name (e.g., ecommerce), which helps to prevent naming conflicts between different projects. When we generate code for our services or clients using this service definition with a package, the same packages (unless we explicitly specify a different package for code generation) are created in the respective programming language (of course only if the language supports the notion of a package) with which our code is generated. We can also define package names with version numbers like ecommerce.v1 and ecommerce.v2. So future major changes to the API can coexist in the same codebase.

Note

Commonly used IDEs (integrated development environments) such as IntelliJ IDEA, Eclipse, VSCode, etc., now have plug-ins to support protocol buffers. You can install the plug-in to your IDE and easily create a protocol buffer definition for your service.

One other process that should be mentioned here is importing from another proto file. If we need to use the message types defined in other proto files, we can import them and our protocol buffer definition. For example, if we want to use the StringValue type (google.protobuf.StringValue) defined in the wrappers.proto file, we can import the google/protobuf/wrappers.proto file in our definition as follows:

syntax = "proto3";

import "google/protobuf/wrappers.proto";

package ecommerce;
...

Once you complete the specification of the service definition, you can proceed to the implementation of the gRPC service and the client.

Implementation

Let’s implement a gRPC service with the set of remote methods that we specified in the service definition. These remote methods are exposed by the server and the gRPC client connects to the server and invokes those remote methods.

As illustrated in Figure 2-2, we first need to compile the ProductInfo service definition and generate source code for the chosen language. Out of the box, gRPC is supported by all the popular languages like Java, Go, Python, Ruby, C, C++, Node, etc. You can choose which language to use when implementing the service or client. gRPC also works across multiple languages and platforms, which means you can have your server written in one language and your client written in another language in your application. In our sample, we will develop our client and server in both the Go and Java languages, so you can follow whichever implementation you prefer to use.

In order to generate source code from the service definition, we can either manually compile the proto file using the protocol buffer compiler or we can use build automation tools like Bazel, Maven, or Gradle. Those automation tools already have a set of rules defined to generate the code when building the project. Often it is easier to integrate with an existing build tool to generate the source code of the gRPC service and client.

client-server interaction of Product Info service
Figure 2-2. A microservice and a consumer based on a service definition

In this sample, we’ll use Gradle to build the Java application and use the Gradle protocol buffer plug-in to generate the service and client code. For the Go application, we’ll use the protocol buffer compiler and generate the code.

Let’s walk through implementing a gRPC server and client in Go and Java. Before we do this, make sure you have installed Java 7 or higher and Go 1.11 or higher on your local machine.

Developing a Service

When you generate the service skeleton code, you will get the low-level code required to establish the gRPC communication, relevant message types, and interfaces. The task of service implementation is all about implementing the interfaces that are generated with the code generation step. Let’s start with implementing the Go service and then we will look at how to implement the same service in the Java language.

Implementing a gRPC service with Go

Implementing the Go service has three steps. First, we need to generate the stubs for the service definition, then we implement the business logic of all the remote methods of the service, and finally, we create a server listening on a specified port and register the service to accept client requests. Let’s start by creating a new Go module. Here we are going to create one module and a subdirectory inside the module; the module productinfo/service is used to keep the service code and the subdirectory (ecommerce) is used to keep the autogenerated stub file. Create a directory inside the productinfo directory and call it service. Navigate inside to the service directory and execute the following command to create the module productinfo/service:

go mod init productinfo/service

Once you create the module and create a subdirectory inside the module, you will get a module structure as follows:

└─ productinfo
           └─ service
                 ├── go.mod
                 ├ . . .
                 └── ecommerce
                         └── . . .

We also need to update the go.mod file with the dependencies with the specific version as shown in the following:

module productinfo/service

require (
  github.com/gofrs/uuid v3.2.0
  github.com/golang/protobuf v1.3.2
  github.com/google/uuid v1.1.1
  google.golang.org/grpc v1.24.0
)
Note

From Go 1.11 onwards, a new concept called modules has been introduced that allows developers to create and build Go projects outside GOPATH. To create a Go module, we need to create a new directory anywhere outside $GOPATH/src and inside the directory, we need to execute the command to initialize the module with a module name like the following:

go mod init <module_name>

Once you initialize the module, a go.mod file will be created inside the root of the module. And then we can create our Go source file inside the module and build it. Go resolves imports by using the specific dependency module versions listed in go.mod.

Generating client/server stubs

Now we’ll generate client/server stubs manually, using the protocol buffer compiler. To do that, we need to fulfill a set of prerequisites as listed here:

  • Download and install the latest protocol buffer version 3 compiler from the GitHub release page.

Note

When downloading the compiler, you need to choose the compiler that suits your platform. For example, if you are using a 64-bit Linux machine and you need to get a protocol buffer compiler version x.x.x, you need to download the protoc-x.x.x-linux-x86_64.zip file.

  • Install the gRPC library using the following command:

go get -u google.golang.org/grpc
  • Install the protoc plug-in for Go using the following command:

go get -u github.com/golang/protobuf/protoc-gen-go

Once we fulfill all the prerequisites, we can generate the code for the service definition by executing the protoc command as shown here:

protoc -I ecommerce  1
  ecommerce/product_info.proto  2
  --go_out=plugins=grpc:<module_dir_path>/ecommerce 3
1

Specifies the directory path where the source proto file and dependent proto files exist (specified with the --proto_path or -I command-line flag). If you do not specify a value, the current directory is used as the source directory. Inside the directory, we need to arrange the dependent proto files in accordance with the package name.

2

Specifies the proto file path you want to compile. The compiler will read the file and generate the output Go file.

3

Specifies the destination directory where you want the generated code to go.

When we execute the command, a stub file (product_info.pb.go) will be generated inside the given subdirectory (ecommerce) in the module. Now that we have generated the stubs, we need to implement our business logic using the generated code.

Implementing business logic

First, let’s create a new Go file named productinfo_service.go inside the Go module (productinfo/service) and implement the remote methods as shown in Example 2-6.

Example 2-6. gRPC service implementation of ProductInfo service in Go
package main

import (
  "context"
  "errors"
  "log"

"github.com/gofrs/uuid"
pb "productinfo/service/ecommerce" 1

)

// server is used to implement ecommerce/product_info.
type server struct{ 2
  productMap map[string]*pb.Product
}

// AddProduct implements ecommerce.AddProduct
func (s *server) AddProduct(ctx context.Context,
                    in *pb.Product) (*pb.ProductID, error) { 356
  out, err := uuid.NewV4()
  if err != nil {
     return nil, status.Errorf(codes.Internal,
         "Error while generating Product ID", err)
  }
  in.Id = out.String()
  if s.productMap == nil {
     s.productMap = make(map[string]*pb.Product)
  }
  s.productMap[in.Id] = in
  return &pb.ProductID{Value: in.Id}, status.New(codes.OK, "").Err()

}

// GetProduct implements ecommerce.GetProduct
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID)
                              (*pb.Product, error) { 456
  value, exists := s.productMap[in.Value]
  if exists {
    return value, status.New(codes.OK, "").Err()
  }
  return nil, status.Errorf(codes.NotFound, "Product does not exist.", in.Value)

}
1

Import the package that contains the generated code we just created from the protobuf compiler.

2

The server struct is an abstraction of the server. It allows attaching service methods to the server.

3

The AddProduct method takes Product as a parameter and returns a ProductID. Product and ProductID structs are defined in the product_info.pb.go file, which is autogenerated from the product_info.proto definition.

4

The GetProduct method takes ProductID as a parameter and returns a Product.

5

Both methods also have a Context parameter. A Context object contains metadata such as the identity of the end user authorization tokens and the request’s deadline, and it will exist during the lifetime of the request.

6

Both methods return an error in addition to the return value of the remote method (methods have multiple return types). These errors are propagated to the consumers and can be used for error handling at the consumer side.

That’s all you have to do to implement the business logic of the ProductInfo service. Then we can create a simple server that hosts the service and accepts requests from the client.

Creating a Go server

To create the server in Go, let’s create a new Go file named main.go inside the same Go package (productinfo/service) and implement the main method as shown in Example 2-7.

Example 2-7. gRPC server implementation to host ProductInfo service in Go
package main

import (
  "log"
  "net"

  pb "productinfo/service/ecommerce" 1
  "google.golang.org/grpc"
)

const (
  port = ":50051"
)

func main() {
  lis, err := net.Listen("tcp", port) 2
  if err != nil {
     log.Fatalf("failed to listen: %v", err)
  }
  s := grpc.NewServer() 3
  pb.RegisterProductInfoServer(s, &server{}) 4

  log.Printf("Starting gRPC listener on port " + port)
  if err := s.Serve(lis); err != nil { 5
     log.Fatalf("failed to serve: %v", err)
  }
}
1

Import the package that contains the generated code we just created from the protobuf compiler.

2

TCP listener that we want the gRPC server to bind to is created on the port (50051).

3

New gRPC server instance is created by calling gRPC Go APIs.

4

The service implemented earlier is registered to the newly created gRPC server by calling generated APIs.

5

Start listening to the incoming messages on the port (50051).

Now we have completed building a gRPC service for our business use case in the Go language. And also we created a simple server that will expose service methods and accept messages from gRPC clients.

If you prefer using Java for building a service, we can implement the same service using Java. The implementation procedure is quite similar to Go. So, let’s create the same service using the Java language. However, if you are interested in building a client application in Go instead, go directly to “Developing a gRPC Client”.

Implementing a gRPC Service with Java

When creating a Java gRPC project, the best approach is to use an existing build tool like Gradle, Maven, or Bazel because it manages all dependencies and code generation, etc. In our sample, we will use Gradle to manage the project and we’ll discuss how to create a Java project using Gradle and how to implement the business logic of all remote methods of the service. Finally, we’ll create a server and register the service to accept client requests.

Note

Gradle is a build automation tool that supports multiple languages, including Java, Scala, Android, C/C++, and Groovy, and is closely integrated with development tools like Eclipse and IntelliJ IDEA. You can install Gradle on your machine by following the steps given on the official page.

Setting up a Java project

Let’s first create a Gradle Java project (product-info-service). Once you have then created the project, you will get a project structure like the following:

 product-info-service

  ├── build.gradle
  ├ . . .
  └── src
      ├── main
      │   ├── java
      │   └── resources
      └── test
          ├── java
          └── resources

Under the src/main directory, create a proto directory and add our ProductInfo service definition file (.proto file) inside the proto directory.

Next, you need to update the build.gradle file and add dependencies and the protobuf plug-in for Gradle. Update the build.gradle file as shown in Example 2-8.

Example 2-8. Gradle configuration for gRPC Java project
apply plugin: 'java'
apply plugin: 'com.google.protobuf'

repositories {
   mavenCentral()
}


def grpcVersion = '1.24.1' 1

dependencies { 2
   compile "io.grpc:grpc-netty:${grpcVersion}"
   compile "io.grpc:grpc-protobuf:${grpcVersion}"
   compile "io.grpc:grpc-stub:${grpcVersion}"
   compile 'com.google.protobuf:protobuf-java:3.9.2'
}

buildscript {
   repositories {
       mavenCentral()
   }
   dependencies { 3

       classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
   }
}

protobuf { 4
   protoc {
       artifact = 'com.google.protobuf:protoc:3.9.2'
   }
   plugins {
       grpc {
           artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
       }
   }
   generateProtoTasks {
       all()*.plugins {
           grpc {}
       }
   }
}

sourceSets { 5
   main {
       java {
           srcDirs 'build/generated/source/proto/main/grpc'
           srcDirs 'build/generated/source/proto/main/java'
       }
   }
}

jar { 6
   manifest {
       attributes "Main-Class": "ecommerce.ProductInfoServer"
   }
   from {
       configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
   }
}

apply plugin: 'application'

startScripts.enabled = false
1

gRPC Java library version used in the Gradle project.

2

External dependencies we need to use in this project.

3

Gradle protobuf plug-in version we are using in the project. Use plug-in version 0.7.5 if your Gradle version is lower than 2.12.

4

In the protobuf plug-in, we need to specify the protobuf compiler version and protobuf Java executable version.

5

This is to inform IDEs like IntelliJ IDEA, Eclipse, or NetBeans about the generated code.

6

Configure the main class to use when running the application.

Then run the following command to build the library and generate stub code from the protobuf build plug-in:

$ ./gradle build

Now we have the Java project ready with autogenerated code. Let’s implement the service interface and add business logic to the remote methods.

Implementing business logic

To start with, let’s create the Java package (ecommerce) inside the src/main/java source directory and create a Java class (ProductInfoImpl.java) inside the package. Then we’ll implement the remote methods as shown in Example 2-9.

Example 2-9. gRPC service implementation of ProductInfo service in Java
package ecommerce;

import io.grpc.Status;
import io.grpc.StatusException;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class ProductInfoImpl extends ProductInfoGrpc.ProductInfoImplBase { 1

   private Map productMap = new HashMap<String, ProductInfoOuterClass.Product>();

   @Override
  public void addProduct(
       ProductInfoOuterClass.Product request,
      io.grpc.stub.StreamObserver
           <ProductInfoOuterClass.ProductID> responseObserver ) { 24
       UUID uuid = UUID.randomUUID();
       String randomUUIDString = uuid.toString();
       productMap.put(randomUUIDString, request);
       ProductInfoOuterClass.ProductID id =
           ProductInfoOuterClass.ProductID.newBuilder()
           .setValue(randomUUIDString).build();
       responseObserver.onNext(id); 5
       responseObserver.onCompleted(); 6
   }

   @Override
  public void getProduct(
       ProductInfoOuterClass.ProductID request,
       io.grpc.stub.StreamObserver
            <ProductInfoOuterClass.Product> responseObserver ) { 34
       String id = request.getValue();
       if (productMap.containsKey(id)) {
           responseObserver.onNext(
                (ProductInfoOuterClass.Product) productMap.get(id)); 5
           responseObserver.onCompleted(); 6
       } else {
           responseObserver.onError(new StatusException(Status.NOT_FOUND)); 7
       }
   }
}
1

Extend the abstract class (ProductInfoGrpc.ProductInfoImplBase) that is generated from the plug-in. This will allow adding business logic to AddProduct and GetProduct methods defined in the service definition.

2

The AddProduct method takes Product(ProductInfoOuterClass.Product) as a parameter. The Product class is defined in the ProductInfoOuterClass class, which is generated from the service definition.

3

The GetProduct method takes ProductID(ProductInfoOuterClass.ProductID) as a parameter. The ProductID class is defined in the ProductInfoOuterClass class, which is generated from the service definition.

4

The responseObserver object is used to send the response back to the client and close the stream.

5

Send a response back to the client.

6

End the client call by closing the stream.

7

Send an error back to the client.

That’s all you need to do to implement the business logic of the ProductInfo service in Java. Then we can create a simple server that hosts the service and accepts requests from the client.

Creating a Java server

In order to expose our service to the outside, we need to create a gRPC server instance and register our ProductInfo service to the server. The server will listen on the specified port and dispatch all requests to the relevant service. Let’s create a main class (ProductInfoServer.java) inside the package as shown in Example 2-10.

Example 2-10. gRPC server implementation to host ProductInfo service in Java
package ecommerce;

import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;

public class ProductInfoServer {

   public static void main(String[] args)
           throws IOException, InterruptedException {
       int port = 50051;
       Server server = ServerBuilder.forPort(port) 1
               .addService(new ProductInfoImpl())
               .build()
               .start();
       System.out.println("Server started, listening on " + port);
       Runtime.getRuntime().addShutdownHook(new Thread(() -> { 2
           System.err.println("Shutting down gRPC server since JVM is " +
                "shutting down");
           if (server != null) {
               server.shutdown();
           }
           System.err.println(Server shut down");
       }));
       server.awaitTermination(); 3
   }
}
1

Server instance is created on port 50051. This is the port we want the server to bind to and where it will listen to incoming messages. Our ProductInfo service implementation is added to the server.

2

A runtime shutdown hook is added to shut down the gRPC server when JVM shuts down.

3

At the end of the method, the server thread is held until the server gets terminated.

Now we are done with the implementation of the gRPC service in both languages. We can then proceed to the implementation of the gRPC client.

Developing a gRPC Client

As we did with the gRPC service implementation, we can now discuss how to create an application to talk with the server. Let’s start off with the generation of the client-side stubs from the service definition. On top of the generated client stub, we can create a simple gRPC client to connect with our gRPC server and invoke the remote methods that it offers.

In this sample, we are going to write client applications in both the Java and Go languages. But you don’t need to create your server and client in the same language, or run them on the same platform. Since gRPC works across languages and platforms, you can create them in any supported language. Let’s discuss the Go implementation first. If you are interested in the Java implementation, you may skip the next section and go directly into the Java client.

Implementing a gRPC Go client

Let’s start by creating a new Go module (productinfo/client) and subdirectory (ecommerce) inside the module. In order to implement the Go client application, we also need to generate the stub as we have done when implementing the Go service. Since we need to create the same file (product_info.pb.go) and need to follow the same steps to generate the stubs, we are not going to mention it here. Please refer to “Generating client/server stubs” to generate stub files.

Let’s create a new Go file named productinfo_client.go inside the Go module (productinfo/client) and implement the main method to invoke remote methods as shown in Example 2-11.

Example 2-11. gRPC client application in Go
package main

import (
  "context"
  "log"
  "time"

  pb "productinfo/client/ecommerce" 1
  "google.golang.org/grpc"

)

const (
  address = "localhost:50051"
)

func main() {

  conn, err := grpc.Dial(address, grpc.WithInsecure()) 2
  if err != nil {
     log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close() 7
  c := pb.NewProductInfoClient(conn) 3

  name := "Apple iPhone 11"
  description := `Meet Apple iPhone 11. All-new dual-camera system with
              Ultra Wide and Night mode.`
  price := float32(1000.0)
  ctx, cancel := context.WithTimeout(context.Background(), time.Second) 4
  defer cancel()
  r, err := c.AddProduct(ctx,
         &pb.Product{Name: name, Description: description, Price: price})  5
  if err != nil {
     log.Fatalf("Could not add product: %v", err)
  }
  log.Printf("Product ID: %s added successfully", r.Value)

  product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value}) 6
  if err != nil {
    log.Fatalf("Could not get product: %v", err)
  }
  log.Printf("Product: ", product.String())

}
1

Import the package that contains the generated code we created from the protobuf compiler.

2

Set up a connection with the server from the provided address (“localhost:50051”). Here we create an unsecured connection between client and server.

3

Pass the connection and create a stub. This stub instance contains all the remote methods to invoke the server.

4

Create a Context to pass with the remote call. Here the Context object contains metadata such as the identity of the end user, authorization tokens, and the request’s deadline and it will exist during the lifetime of the request.

5

Call addProduct method with product details. This returns a product ID if the action completed successfully. Otherwise it returns an error.

6

Call getProduct with the product ID. This returns product details if the action completed successfully. Otherwise it returns an error.

7

Close the connection when everything is done.

Now we have completed building the gRPC client in the Go language. Let’s next create a client using the Java language. This is not a mandatory step to follow. If you are also interested in building a gRPC client in Java, you can continue; otherwise, you can skip the next section and go directly to “Building and Running”.

Implementing a Java client

In order to create a Java client application, we also need to set up a Gradle project (product-info-client) and generate classes using the Gradle plug-in as we did when implementing the Java service. Please follow the steps in “Setting up a Java project” to set up a Java client project.

Once you generate the client stub code for your project via the Gradle build tool, let’s create a new class called ProductInfoClient inside the ecommerce package and add the content in Example 2-12.

Example 2-12. gRPC client application in Java
package ecommerce;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

import java.util.logging.Logger;

/**
* gRPC client sample for productInfo service.
*/
public class ProductInfoClient {

   public static void main(String[] args) throws InterruptedException {
       ManagedChannel channel = ManagedChannelBuilder
           .forAddress("localhost", 50051) 1
           .usePlaintext()
           .build();

       ProductInfoGrpc.ProductInfoBlockingStub stub =
              ProductInfoGrpc.newBlockingStub(channel); 2

       ProductInfoOuterClass.ProductID productID = stub.addProduct(    3
               ProductInfoOuterClass.Product.newBuilder()
                       .setName("Apple iPhone 11")
                       .setDescription("Meet Apple iPhone 11. " +
                            All-new dual-camera system with " +
                            "Ultra Wide and Night mode.");
                       .setPrice(1000.0f)
                       .build());
       System.out.println(productID.getValue());

       ProductInfoOuterClass.Product product = stub.getProduct(productID);  4
       System.out.println(product.toString());
       channel.shutdown(); 5
   }
}
1

Create a gRPC channel specifying the server address and port we want to connect to. Here we are trying to connect to a server running on the same machine and listening on port 50051. We also enable plaintext, which means we are setting up an unsecured connection between client and server.

2

Create the client stub using the newly created channel. We can create two types of stubs. One is the BlockingStub, which waits until it receives a server response. The other one is the NonBlockingStub, which doesn’t wait for server response, but instead registers an observer to receive the response. In this example, we use a BlockingStub to make the client simple.

3

Call addProduct method using the product details. This returns a product ID if the action completed successfully.

4

Call getProduct with the product ID. Returns the product details if the action completed successfully.

5

Close the connection when everything is done so that the network resources that we used in our application are safely returned back after we are finished.

Now we have finished developing the gRPC client. Let’s make the client and server talk to each other.

Building and Running

It’s time to build and run the gRPC server and client applications that we have created. You can deploy and run a gRPC application on your local machine, on a virtual machine, on Docker, or on Kubernetes. In this section, we will discuss how to build and run the gRPC server and client applications on a local machine.

Note

We will cover how to deploy and run gRPC applications on Docker and Kubernetes environments in Chapter 7.

Let’s run the gRPC server and client applications that we have just developed in your local machine. Since our server and client applications are written in two languages, we are going to build the server application separately.

Building a Go Server

When we implement a Go service, the final package structure in the workspace looks like the following:

└─ productinfo
           └─ service
                 ├─ go.mod
                 ├─ main.go
                 ├─ productinfo_service.go
                 └─ ecommerce
                    └── product_info.pb.go

We can build our service to generate a service binary (bin/server). In order to build, first go to the Go module root directory location (productinfo/service) and execute the following shell command:

$ go build -i -v -o bin/server

Once the build is successful, an executable file (bin/server) is created under the bin directory.

Next, let’s set up the Go client!

Building a Go Client

When we implement a Go client, the package structure in the workspace looks like:

└─ productinfo
           └─ client
                ├─ go.mod
                ├──main.go
                └─ ecommerce
                       └── product_info.pb.go

We can build the client code the same way we built the Go service using the following shell command:

$ go build -i -v -o bin/client

Once the build is successful, an executable file (bin/client) is created under the bin directory. The next step is to run the files!

Running a Go Server and Client

We’ve just built a client and a server. Let’s run them on separate terminals and make them talk to each other:

// Running Server
$ bin/server
2019/08/08 10:17:58 Starting gRPC listener on port :50051

// Running Client
$ bin/client
2019/08/08 11:20:01 Product ID: 5d0e7cdc-b9a0-11e9-93a4-6c96cfe0687d
added successfully
2019/08/08 11:20:01 Product: id:"5d0e7cdc-b9a0-11e9-93a4-6c96cfe0687d"
        name:"Apple iPhone 11"
        description:"Meet Apple iPhone 11. All-new dual-camera system with
        Ultra Wide and Night mode."
        price:1000

Next we’ll build a Java server.

Building a Java Server

Since we implement the Java service as a Gradle project, we can easily build the project using the following command:

$ gradle build

Once the build is successful, the executable JAR (server.jar) file is created under the build/libs directory.

Building a Java Client

Just as with a service, we can easily build the project using the following command:

$ gradle build

Once the build is successful, the executable JAR (client.jar) file is created under the build/libs directory.

Running a Java Server and Client

We’ve now built both a client and server in the Java language. Let’s run them:

$ java -jar build/libs/server.jar
INFO: Server started, listening on 50051

$ java -jar build/libs/client.jar
INFO: Product ID: a143af20-12e6-483e-a28f-15a38b757ea8 added successfully.
INFO: Product: name: "Apple iPhone 11"
description: "Meet Apple iPhone 11. All-new dual-camera system with
Ultra Wide and Night mode."
price: 1000.0

Now we have built and run our sample successfully on local machines. Once we successfully run the client and server, the client application first invokes the addProduct method with product details and receives the product identifier of the newly added product as the response. Then it retrieves the newly added product details by calling the getProduct method with the product identifier. As we mentioned earlier in this chapter, we don’t need to write the client in the same language to talk with the server. We can run a gRPC Java server and Go client and it will work without any issue.

That brings us to the end of the chapter!

Summary

When you develop a gRPC application, you first define a service interface definition using protocol buffers, a language-agnostic, platform-neutral, extensible mechanism for serializing structured data. Next, you generate server-side and client-side code for the programming language of your choice, which simplifies the server- and client-side logic by providing the low-level communication abstractions. From the server side, you implement the logic of the method that you expose remotely and run a gRPC server that binds the service. On the client side, you connect to the remote gRPC server and invoke the remote method using the generated client-side code.

This chapter is mainly about getting hands-on experience with developing and running gRPC server and client applications. The experience you gain by following the session is quite useful when building a real-world gRPC application because irrespective of which language you are using, you need similar steps to build a gRPC application. So, in the next chapter, we will further extend the concepts and technologies you learned to build real-world use cases.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset