Developing Microservices & APIs using gRPC

  • API to exchange data. e.g. Below can be APIs formats :-
  • The data format itself. e.g. it can be JSON, XML or binary.
  • Efficiency of the API and Load-balancing. There can be instances when we have too much of data and also instances, when data transferred is too less.
  • The error patterns.
  • Rate limiting and API authentication.
  • Scaling for millions of requests.
  • Latency of the APIs.
  • Inter-operability across many platforms & programming languages.
  • Efficient Monitoring, logging and Alerting.
  • It allows us to define the REQUEST / RESPONSE for RPC(Remote Procedure Calls) and handles everything out of the box.
  • Its an efficient and fast way of APIs invocation.
  • Its build on top of HTTP/2.
  • It have low latency and also is language independent.
  • It supports plugging of authentication, load-balancing, logging and monitoring in an easy style.
  • We first define the messages and services using Protocol-Buffers and rest of the code is generated for us automatically.
  • One .proto file works for over 12 Programming Languages and can scale to millions of requests per second.
  • Client basically sends the Proto-request to the server and server in-turn sends back the proto-response to client.
  • Unary → This is how a traditional API looks like with usual request/response. The client shall send one message to the server and would receive one response from the server. Unary RPC calls are well suited, when data to be transferred is small.
  • Server/Client/Bidirectional Streaming → With HTTP/2, we have streaming capabilities i.e. server and client can push multiple messages as part of one request. We can use these streaming RPC calls , if we have scaling and optimisation issues.
  • gRPC servers are Asynchronous by default i.e. they doesn’t blocks the threads on request. Therefore each gRPC server can serve millions of requests per second in parallel.
  • gRPC clients can be either synchronous OR Asynchronous. Also, gRPC clients are well positioned to perform the client side load-balancing as well.
  • REST shall be using JSON as data-transfer standard which would be slower, bigger and text-based while gRPC uses the protobuf as data-transfer standard which are smaller and faster in nature.
  • REST uses the HTTP/1.1 while gRPC uses the HTTP/2 which is lot more faster and efficient.
  • REST only supports Client to server calls while gRPC supports bidirectional and async calls as well.
  • REST only supports request/response while gRPC supports streaming capabilities as well.
  • REST is purely Resource-oriented while gRPC is purely API oriented with free-design.
  • REST supports auto-code-generation using swagger and openAPI as 2nd class citizens while gRPC supports auto-code-generation using prtobuf as 1st class citizens.
  • REST is Verbs based and thus we have to basic plumbing ourselves while gRPC is RPC based i.e. we can invoke the functions at server easily.
syntax = "proto3";

package test;

option java_package = "com.proto.test";

option java_multiple_files = true;

message TestMessage {}

service TestService {}
public static void main(String[] args) throws SSLException {
System.out.println("Hello I'm a gRPC client");
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();

TestServiceGrpc.TestServiceBlockingStub synchronousClient = TestServiceGrpc.newBlockingStub(channel);
TestServiceGrpc.TestServiceFutureStub asynchronousClient = TestServiceGrpc.newFutureStub(channel);

System.out.println("Shutting down the channel.");
channel.shutdown();

}
syntax = "proto3";

package greet;

option java_package = "com.proto.greet";
option java_multiple_files = true;

message Greeting {
string first_name = 1;
string last_name = 2;
}

message GreetRequest {
Greeting greeting = 1;
}

message GreetResponse {
string result = 1;
}

service GreetService {
// Unary API
rpc Greet(GreetRequest) returns (GreetResponse) {};
}
  • Every service should have a suffix ‘Service’ in its name.
  • Every request should have a suffix ‘Request’ and every response should have ‘Response’ in its name.
  • In case, the name of the fields of any message are multi-word, we should have ‘_’ as a separator.
public class GreetServiceImpl extends GreetServiceGrpc.GreetServiceImplBase {

@Override
public void greet(GreetRequest request, StreamObserver<GreetResponse> responseObserver) {
// Extract information from Request as it is received.
Greeting greetRequest = request.getGreeting();
String firstName = greetRequest.getFirstName();

// Frame the response, to be sent back
String framedResponse = "Welcome " + firstName;

GreetResponse greetResponse = GreetResponse.newBuilder().setResult(framedResponse).build();

// Sending back the response to the client
responseObserver.onNext(greetResponse);

// complete the call
responseObserver.onCompleted();
}
}
public class GreetingServer {

public static void main(String[] args) throws IOException, InterruptedException {
System.out.println("Starting the gRPC Server with GreetService hossted in it.");

// plaintext server
Server server = ServerBuilder.forPort(50051)
.addService(new GreetServiceImpl())
.build();

server.start();

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Received Shutdown Request");
server.shutdown();
System.out.println("Successfully stopped the server");
}));

server.awaitTermination();
}
}
public static void main(String[] args) throws SSLException {
System.out.println("Starting our gRPC client");

ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext() // Not recommended for production.
.build();

// Build the protobuf Greeting message.
Greeting greetingRequestMessage = Greeting.newBuilder()
.setFirstName("Harey")
.setLastName("Krishna")
.build();

// Build the GreetingRequest, which has to be sent to the server.
GreetRequest greetRequest = GreetRequest.newBuilder()
.setGreeting(greetingRequestMessage)
.build();

// Create the Synchronous blocking client.
GreetServiceGrpc.GreetServiceBlockingStub greetClient = GreetServiceGrpc.newBlockingStub(channel);

// Invoke the function (being implemented at server).
// This is actually a N/w call, but looks like method call.
GreetResponse greetResponse = greetClient.greet(greetRequest);

System.out.println("The result thus obtained here is: "+ greetResponse.getResult());
channel.shutdown();
}
  • Server has to send big-data back to the client. Say example, the response consists of 10 GB, then sending all of this data at once presents the risk of n/w failure or call being failed eternally. Solution to this can be, if server sends stream of messages each of 100 KB each. In this case, there are high chances that, this transfer shall succeed.
  • Server needs to PUSH the data to client, without having the client-request. e.g. Live feed, etc. Basically, Server is pushing the data to client, without the client being asking for it.
service GreetService { 
// Server Streaming API
rpc GreetServerStreaming(GreetRequest) returns (stream GreetResponse) {};

}
public class GreetServerStreamingServiceImpl extends GreetServiceGrpc.GreetServiceImplBase {

@Override
public void greetServerStreaming(GreetRequest request, StreamObserver<GreetResponse> responseObserver) {
// Extract information from Request as it is received.
String firstName = request.getGreeting().getFirstName();

// Server shall be sending stream of responnses.
try {
for(int i=1; i <10; i++) {
// Frame the response
String framedResponse = "Welcome " + firstName + " for the " + i + " time.";
GreetResponse greetResponse = GreetResponse.newBuilder()
.setResult(framedResponse).build();

// Send back the response
responseObserver.onNext(greetResponse);

Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// complete the call
responseObserver.onCompleted();
}
}
}
// Receive the Streaming response in blocking manner.
greetClient.greetServerStreaming(greetRequest)
.forEachRemaining(greetResponse -> {
System.out.println("Good-Morning: "+ greetResponse.getResult());
});
  • When the client needs to send big-data, like uploading something.
  • When the server processing is quite expensive and should happen as and when the client sends the data. For e.g. Say client has to send 10K messages to server, then Server can start processing the data one by one, rather than it starts processing after client sends all 10K messages.
  • When the client needs to PUSH the data to the server, without really expecting a response back from it.
// Client Streaming API
rpc GreetClientStreaming(stream GreetRequest) returns (GreetResponse) {};
@Override
public StreamObserver<GreetRequest> greetClientStreaming(StreamObserver<GreetResponse> responseObserver) {
StreamObserver<GreetRequest> greetRequestStreamObserver = new StreamObserver<GreetRequest>() {

String result = "";

@Override
public void onNext(GreetRequest value) {
// Client Sends a messageRecord
result += "Hello " + value.getGreeting().getFirstName() + " !.";
}

@Override
public void onError(Throwable t) {
// Client Sends an Error
}

@Override
public void onCompleted() {
// Client is done and we want to send the response back.
responseObserver.onNext(
GreetResponse.newBuilder()
.setResult(result)
.build()
);
responseObserver.onCompleted();
}
};
return greetRequestStreamObserver;
}
private static void doClientStreamingCall(ManagedChannel channel) throws InterruptedException {
// Create the ASynchronous non-blocking client.
GreetServiceGrpc.GreetServiceStub greetClient = GreetServiceGrpc.newStub(channel);

CountDownLatch countDownLatch = new CountDownLatch(1);

StreamObserver<GreetRequest> requestStreamObserver = greetClient.greetClientStreaming(
new StreamObserver<GreetResponse>() {
@Override
public void onNext(GreetResponse value) {
// We get a response back from server. It shall be called ONCE.
System.out.println("Received some response from Server.");
System.out.println(value.getResult());
}

@Override
public void onError(Throwable t) {
// We get a Error back from server. It shall be called ONCE.

}

@Override
public void onCompleted() {
// The server is done. Shall be called after onNext method.
countDownLatch.countDown();

}
}
);

System.out.println("Sending Message1 to Server.");
requestStreamObserver.onNext(GreetRequest.newBuilder()
.setGreeting(Greeting.newBuilder()
.setFirstName("Harey")
.build())
.build());

System.out.println("Sending Message2 to Server.");
requestStreamObserver.onNext(GreetRequest.newBuilder()
.setGreeting(Greeting.newBuilder()
.setFirstName("Krishna")
.build())
.build());

// Tell the server that client is done sending the data.
requestStreamObserver.onCompleted();

countDownLatch.await(3L, TimeUnit.SECONDS);
}
  • When the client and server both have to send a lot of data asynchronously.
  • “Chat” Protocol. Here both client and server sends messages to each other.
  • Long running connections, where data can be streamed back and forth between client and server.
// Bidirectional Streaming API
rpc GreetBidorectionalStreaming(stream GreetRequest) returns (stream GreetResponse) {};
@Override
public StreamObserver<GreetRequest> greetBidorectionalStreaming(StreamObserver<GreetResponse> responseObserver) {
StreamObserver<GreetRequest> greetRequestStreamObserver = new StreamObserver<GreetRequest>() {

String result = "";

@Override
public void onNext(GreetRequest value) {
// Client Sends a messageRecord and In turn, Server sends back a response.
result += "Hello " + value.getGreeting().getFirstName() + " !.";
GreetResponse greetResponse = GreetResponse.newBuilder()
.setResult(result)
.build();
responseObserver.onNext(greetResponse);
}

@Override
public void onError(Throwable t) {
// Client Sends an Error
}

@Override
public void onCompleted() {
// Client is done now.
responseObserver.onCompleted();
}
};
return greetRequestStreamObserver;
}
private static void doBidirectionalStreamingCall(ManagedChannel channel) throws InterruptedException {
// Create the ASynchronous non-blocking client.
GreetServiceGrpc.GreetServiceStub greetClient = GreetServiceGrpc.newStub(channel);

CountDownLatch countDownLatch = new CountDownLatch(1);

StreamObserver<GreetRequest> requestStreamObserver = greetClient.greetBidorectionalStreaming(
new StreamObserver<GreetResponse>() {
@Override
public void onNext(GreetResponse value) {
// Everytime the response comes back from server, this method is going to be called.
System.out.println("Received some response from Server." + value.getResult());
}

@Override
public void onError(Throwable t) {
countDownLatch.countDown();
}

@Override
public void onCompleted() {
// The server is done sending all the responses for 1 cycle.
countDownLatch.countDown();
}
}
);

System.out.println("Sending Stream of messages to the Server.");
Arrays.asList("Honesty", "is", "the", "best", "policy").forEach(
item -> {
System.out.println("Sending message to the server now.");
requestStreamObserver.onNext(GreetRequest.newBuilder().setGreeting(Greeting.newBuilder().setFirstName(item).build()).build());
}
);

// Tell the server that client is done sending the data.
requestStreamObserver.onCompleted();

countDownLatch.await(3L, TimeUnit.SECONDS);
}
service CalculatorService {
// Unary API
rpc SquareRootFinder(SquareRootRequest) returns (SquareRootResponse) {};
}
@Override
public void squareRootFinder(SquareRootRequest request, StreamObserver<SquareRootResponse> responseObserver) {
// Extract information from Request as it is received.
int inputNumber = request.getInputNumber();

if(inputNumber > 0) {
// Send back the response
double squareRootNumber = Math.sqrt(inputNumber);
responseObserver.onNext(SquareRootResponse.newBuilder()
.setOutputNumber(squareRootNumber).build());
} else {
// Send back the response not possible.
responseObserver
.onError(Status.INVALID_ARGUMENT.withDescription("Number is Not positive.").asRuntimeException());
}
// complete the call
responseObserver.onCompleted();

}
@Override
public void squareRootFinder(SquareRootRequest request, StreamObserver<SquareRootResponse> responseObserver) {
System.out.println("Method control reached to this RPC method.");
// Fetches the current context.
Context context = Context.current();

// Extract information from Request as it is received.
int inputNumber = request.getInputNumber();

if(inputNumber > 0) {
// Send back the response
try {
System.out.println("Computation under progress still.");
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}

if(context.isCancelled()) {
System.out.println("Client has cancelled the request");
} else {
double squareRootNumber = Math.sqrt(inputNumber);
responseObserver.onNext(SquareRootResponse.newBuilder()
.setOutputNumber(squareRootNumber).build());
}
} else {
// Send back the response not possible.
responseObserver
.onError(Status.INVALID_ARGUMENT.withDescription("Number is Not positive.").asRuntimeException());
}
// complete the call
responseObserver.onCompleted();

}
// Invoke the function with Deadline being set.
// This is actually a N/w call, but looks like method call.
try {
System.out.println("Sending request with a deadline of 500 ms.");
SquareRootResponse squareRootResponse = calculatorServiceBlockingStub
.withDeadline(Deadline.after(700, TimeUnit.MILLISECONDS))
.squareRootFinder(squareRootRequest);

System.out.println("Square root response thus received is : " + squareRootResponse.getOutputNumber());
} catch(StatusRuntimeException e) {
if(e.getStatus().equals(Status.DEADLINE_EXCEEDED)) {
System.out.println("Deadline has been exceeded and response not received wven within 500 ms.");
}
e.printStackTrace();
}
  • Step 1.) Lets create our “Certificate Authority private key file” (this shouldn’t be shared in real-life) :-
openssl genrsa -passout pass:1111 -des3 -out ca.key 4096
  • Step 2.) Lets create our “Certificate Authority trust certificate” (this can be shared with users in real-life, as this is a public certificate :-
openssl req -passin pass:1111 -new -x509 -days 365 -key ca.key -out ca.crt -subj "/CN=${SERVER_CN}"
  • Step 3.) Lets create our “Server private key” :-
openssl genrsa -passout pass:1111 -des3 -out server.key 4096
  • Step 4.) Now, Lets Get a certificate signing request from the CA :-
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj "/CN=${SERVER_CN}"
  • Step 5.) We now sign the certificate with the CA we created :-
openssl x509 -req -passin pass:1111 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
  • Step 6.) We convert the server certificate to .pem format (usable by gRPC) :-
openssl pkcs8 -topk8 -nocrypt -passin pass:1111 -in server.key -out server.pem
implementation 'io.grpc:grpc-netty-shaded:1.32.1' // Includes SSL Libraries.
Server server = ServerBuilder.forPort(50051)
.addService(new CalculatorServiceImpl())
.useTransportSecurity(
new File("ssl/server.crt"),
new File("ssl/server.pem"))
.build();
ManagedChannel securedChannel = NettyChannelBuilder.forAddress("localhost", 50051)
.sslContext(GrpcSslContexts.forClient().trustManager(new File("ssl/ca.crt")).build())
.build();
compile 'io.grpc:grpc-services:1.32.1' // Reflections.
Server server = ServerBuilder.forPort(50051)
.addService(new CalculatorServiceImpl())
.addService(ProtoReflectionService.newInstance())
.build();
brew tap ktr0731/evans
brew install evans

--

--

--

Software Engineer for Big Data distributed systems

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
aditya goel

aditya goel

Software Engineer for Big Data distributed systems

More from Medium

Connect to Redis in Go

Hands-On with REDIS | Part 2

Securely Connect to Redis and Utilize Benchmark Tools

Scaling your backend service — System Design