Remote Procedure Call is not a new technology, and if you have java backgrounds, probably you heard of it, or if you worked with WSDL, then understanding RPC won't be tough to grasp.
In a simple word, RPC gives the client the ability to call a procedure from the server, and it’s pretty much like the client is calling a function within its own codebase. The client knows what the function interface is; therefore, it can pass the correct parameters.
grpc is the latest RPC framework developed by Google and became open source by 2015, available in these languages.
How does the client connect to the server?
The client has to import the server lib to have access to interfaces and connect to the server. It’s the server engineer’s responsibility to provide the lib and keep the lib up to date based on the server's latest changes.
When should we use grpc?
grpc is best suited for large scale microservices or an ecosystem with many different services written in multiple languages that communicate with each other.
Like any other technology and tool, you should have a use case for grpc to use it and not pick it up because it looks cool and everyone else is using it. To determine if grpc is the right framework for your service communication, you shall first understand it.
What types of communications are available?
Many know grpc because it provides two types of communication that are:
It is like an API call; One single request goes from client to server, and it blocks till response comes back to the client.
The stream is a type of communication that attracts many engineers and has three sub-types
- Client stream
A client sends its request as a stream.
- Server stream
The server sends its response as a stream.
Client and Server both send stream.
Jumping to the code to understand it better.
This section of the post shows you how to create a simple grpc server/client in Golang. To keep this post short and simple, I only focus on the Unary sample and leave Steam for another post.
grpc uses the proto standard to define the functions and their contracts; in simple words, the Proto file explains the available functions, including their input and output parameters. Later on, we use the proto file to create server and client libs.
The server could choose to implement all or only some of the functions explained in the proto file by using the lib.
The client uses the client lib to communicate with the server.
Create the routes.proto with the below content and locate it inside the routes folder
Move to routes directory and execute the below command to create libs needed by server and client.
Two files are added to the route folder after running the above command.
You can check the content of these files, but you should not change them. These files must be generated again if there is an update in the proto file.
Create a grpc server
Let's explore the code together.
If you are familiar with go, then probably know most of these libraries and their usage.
Each grpc function, by default, accepts the context as a first param, and it’s up to the server to use it or ignore it. Context is a handy feature in Go, and you can read more about it here.
flag module used to read the command params that passed to the server during the execution. This module is needed if your server needs to receive custom params unless you can ignore it. In this example, you can pass the server port during the server execution.
This module is used to printout the message in stdout.
grpc module is needed to setup grpc server
The log module is used for logging in stdout if the server fails to start.
The net module is needed to listen to the specific port; The grpc server uses it to process the incoming request.
- np github.com/MehranJanfeshan/go-lambda/grpc-sample/routes
Here we are importing the module that we created earlier by running the proto command. This module is imported and assigned to the np variable that is used later on in the code.
Setting the server
Here we get the server port that is passed as a parameter when the server started. Also, there is a default value considered in case no parameter is given.
The server could choose to implement all or only a few functionally that explained in the proto file. This freedom brings some challenges with the grpc server expected implementation for each function that descried in the proto file.
Fortunately, this is not a big problem to solve as the autogenerated Go module has a struct called UnimplementedRouteGuidServer that has a simple implementation for all the functions. Similar to the below example.
Having UnimplementedRouteGuidServer as an autogenerated structure helps us create our own version of it by creating a nested struct like what you can see below.
Now that we have a nested struct, we could choose what function to implement; if you come from OOP, consider this action as overriding! This the best that comes to my mind!
Implementing Greeting function
This is a very simple implementation for the Greeting function, and probably you have more complex for production!
It’s time to create the server.
In this example, The main function puts all the pieces together and create the grpc server. In production code, you have to think differently and not put everything in the primary function.
Parse flag to get the value of params, if any.
listening to the server port
Always there is a chance that the port is not available, so you have to handle this exception same the below code.
Creare optional opt for the server.
In this example, we do not have any optional opt for the server, but you could include certificate details, etc., in opts.
Create grpc server object
Register the custom struct and server
Start the server.
I ignore the import section, as is explained earlier, and we start with serverAddr
serverAddr is a variable that holds server address, and it gets its value from command param, and if the param is not given, it uses the default value.
callGreeting accepts grpc client type np.RouteGuidClient. The client is a proxy that facilitates the functionality to call the Server procedure. It has a very similar concept as an object in OOP or functional programming.
It is explained earlier that each grpc function accepts Context as its first param. Therefore we have to create a Context as below.
The servers' current implementation ignores the context, but it should not happen in the real world.
Now it is time to call the Greeting function and pass all the params.
The client's main function is responsible for putting all the pieces together and connecting to the server.
Run the server
go run server/server.go
Run the client
go run client/client.go