Recently we had a chance to work on a project for a client that involved developing inter-service communication. The systems which had to be interconnected were standalone monolithic HTTP servers written in node. After a lot of research on inter-process communication methodologies, we decided we needed some RPC (Remote Procedure Call) framework in place to interconnect existing services.
We then decided to go with Google’s “gRPC” framework using HTTP/2 as the transport layer and Protocol Buffer as an interaction language. gRPC also has SSL authentication between the client module and the server module, which is an excellent security feature provided by the framework. One of the challenges we faced was protocol buffer documentation given by Google maybe a little confusing for beginners. We found it challenging to write proto3 files because it has a very declarative syntax. Proto3 is short for Protocol Buffer 3, and it is a style structured data declaration which is developed by Google.
We will now create a protocol 3 buffer file for a simple use-case which creates a service that returns the multiplication of 2 numbers.
In the snippet, we have defined a Service “CalculatorService” which has a remote stub function named “Multiply”. Now the “Multiply” function has an argument message type “RequestArgument”, and it returns message type “ReturnData”.
The function “Multiply” will have an argument and a return type of message, so the following is a syntax on how to define stub functions in proto3. The detailed data declarations for “RequestArgument” and “ReturnData” are defined as they have a data type specification, a property name and a field number identifier which is a unique number. The input will be two numbers so we have specified two properties namely “num1” and “num2” which will be integers and that is why we have defined it to be of the “int32” data type. The function will return a number as a result after the stub function does the multiplication, so we have made “result” of type “int32”.
These field numbers are used to identify your fields in the message binary format, and should not be changed once your message type is in use.
So now we use this proto3 file to load our service descriptors. Here we have the “CalculatorService” service loaded in the variable named “calculator”. But before this code starts working, we need to implement the service interface generated from our service definition: doing the actual “work” of our service. Running a gRPC server to listen for requests from clients and return the service responses.
We use the “grpc/proto-loader” to load the proto3 file and tell our grpc instance to load in the package definition provided in “calculator.proto”. Then we add the server code to initialize the stub and run the server on the localhost. We also implement the “Multiply” function which will be loaded in the stub and add it to the Service. The constructor on the “grpc” variable created the server for us directly without any added hassle. Here in the above example, we have run an insecure server without SSL credentials. In our project, we also did run gRPC in “insecure mode” as both the servers on a private AWS network. We have also added the “CalculatorService” to the server using the load descriptor variable named “calculator” and also created a function to fill in for the service function named “multiply” which takes 2 arguments “call” and “callback”. “call” variable has information about the caller arguments in the “request” property of the “call” object and to reference them using “num1” and “num2” to get the function caller input of this remote RPC function and “callback” function is similar to a NodeJS callback function used to return the data to the called client module.
Now we’ll look at creating a NodeJS client module.
In the client module file, we again use the service descriptors code to load the proto3 file and then using the “calculator” variable we initialize the client to call the “Multiply” function. We generate two random numbers whose multiplication is to be calculated. Then we call the “Multiply” function using the “client” variable we initialized, and we pass the 2 numbers as an object because the “RequestArgument” type message has 2 properties with the keys “num1” and “num2” and also we send it a node standard callback function to check for errors and to get the response. The callback will be called when the server returns the “ReturnData” message to the initialized client, and then we print the multiplication result as “response.result” because the “ReturnData” message type had a property name of “result” which can be seen in calculator.proto.
All the above was a base reference to implement remote procedure calls using gRPC. Likewise, we used the same server-client architecture implementation in our project, one of the HTTP servers whose services are to be shared along with the gRPC server running in parallel on localhost. The other HTTP server/servers become the client module, hence facilitating exchange information between them and in turn sharing the services them.
gRPC architecture helps us to maintain a modular data flow between independent modules of system applications and to allow us to interconnect pre-existent services and to reduce development time by avoiding unnecessary reimplementing or cloning the same piece of code.
If you want to run this example yourself, please follow the below GitHub link.
Thanks for reading, have a productive day. Keep Coding!