gRPC — the new REST!
REST
is the ubiquitous way of writing APIs for as long as I can remember. Today I want to introduce you to a new way of writing APIs.
Have you met
gRPC
?
gRPC is a relatively new way to write APIs and consume them as if you’re just calling a function.
gRPC
originated from RPC (Remote Procedure Call). The client can just call a stubbed method which will invoke the method at the server side- all the connection details are abstracted at the client with this stub
- it doesn’t use everyone’s favourite
JSON
to send the data over the wire, but uses a new format called Protocol Buffers (protobuf in short) protobuf
is a new way of serializing data in binary format, which cuts down on payload size and readability both
There are lots of pros on using gRPC
as the way to write services over REST
that I went ahead and created this big table of analysis for you all :)
gRPC vs REST
variants
Did I tell you that streaming is a first class citizen in gRPC
due to HTTP/2 protocol. That means there are four types of APIs you can write and use
- no streaming at all just like plain
REST
- it's calledunary
api ingRPC
lingo - client calling once and then server streaming
- client streaming and then the server responding once
- both client and server streaming continuously
defining the interface
Everything starts from a proto
file, which contains the definition of the APIs (methods) that the server will implement and clients can call.
I am going to implement a sum service that would take two numbers and return the result.
Below are the definition of the request and response types along with Sum
service
Once this is done, just running mvn clean install
will generate the request, response and service classes automatically which will be used to add the logic of the API (no sweat!)
If you’re following along, then take a peek at the /target folder and you’ll find all the autogenerated code there :)
making the API ready
Time to see how absolutely lazy it is to create the API
- creating a file (of course),
DemoServiceImpl.java
(cuz that’s how Java folks roll) - once you create the class, extend it with the auto-generated class of the same name with which you defined the service in the proto file (
DemoService
) - the auto-generated class has methods for the service, like shown below
sum
- it generates the boilerplate code so that you can just add the logic
Adding the logic for sum service; simplest logic in the world :P
- you see the response parameter in the method, notice it’s of type
StreamObserver
- this means you can call
response.onNext()
multiple times and finally when you're done you can doresponse.onCompleted()
- this shows that even for a normal (unary) api, you can stream the response.
starting the server
With a handful of boilerplate code we can start the server by writing a main
program in a separate class. The first line shows creating the server and adding the service to it. A new instance of DemoServiceImpl
.
consuming the service
Consuming the service consists of three parts
creating the channel
This specifies the endpoint and port of the service running. gRPC
is language agnostic, which means you can write your service in one language and call it using another. But here I'm just sticking with java.
creating the stub
This is done using the auto-generated classes. Stub can be of two types:
- blocking or sync
- non-blocking
calling the service
This is just calling a method on the stub which corresponds to the actual service running
- all the classes used above are auto-generated, notice the
request
andresponse
types which are the same that were defined in theproto
file - all you need to do is to pass the arguments and call the sum method
this is the magic of gRPC, on the client you’re calling just a method and it invokes the actual remote implementation
streaming both ways
Let’s see how streaming work both ways, and the perfect example of it would be a chat api, where client is sending messages over the same connection and server responding.
adding new definition in the proto file
- note the use of
stream
keyword in the service definition - this tells both the client and server that this would be a bidirectional streaming api
- let’s see how this affects the generated code as the signature of the method will be a little different
Do mvn clean compile
to update the generated-code
adding the chat
method in service
Back in DemoServiceImpl.java
, I magically have the chat method signature which looks like this
@Override
public StreamObserver<ChatRequest> chat(StreamObserver<ChatResponse> responseObserver) {
}
- notice just one argument to the method, as client will be streaming the request, there’s no request object, just the response stream available.
- also see the return type expected out of the
chat
method, it's of typeChatRequest
, interesting...
So let’s implement this
As soon as you try to return the StreamObserver<ChatRequest>
object, you'd see this code generated by the IDE. All you've to do is now add logic inside the three methods generated for you.
return new StreamObserver<ChatRequest>() {
@Override
public void onNext(ChatRequest chatRequest) {
// this will be called for every client message
// notice the argument gives you the client request directly
}
@Override
public void onError(Throwable throwable) {
// when there's an error at client side
}
@Override
public void onCompleted() {
// client is done!
// no more messages would be sent
}
};
Let’s add the logic to reply to this client and reply back using the method argument responseObserver
calling from client
- a similar style of api that was implemented in the server
- same construct of
onNext
andonCompleted
The final output
server says thanks for sending: 1st message from client
server says thanks for sending: 2nd message from client
server says thanks for sending: 3rd message from client
server says thanks for sending: 4th message from client
code
All the code for this post is available here.
Originally published at https://ankeetmaini.dev.