visit
// Imagine a client (an online shop) communicating with a server (payment processor)
// we have an Order object in the shop with an associated item name and price
Order newOrder = new Order();
newOrder.writeItemName("playstation5");
newOrder.writePrice("499.99");
// we call a credit card service to process the payment
// Note: the key here is that the credit card service is on a completely different network node.
// it is not a local method call.
Payment payment = creditCardService.processPayment(parseInt(newOrder.Price, 10))
//...depending on whether or not the payment is successful, you can do further work.
The service definition states which methods can be called remotely, and it also specifies the types of objects being used to initiate an RPC call, as well as the expected return type. Using our example from above, you can imagine that the processPayment
service definition expects a price, and will return a boolean to signify the success or failure of a payment. We’ll see a more concrete example later on.
The basic difference is that instead of using JSON or XML, gRPC adapts a regular RPC framework to use “protocol buffers” or protobuf
as its interface definition language (this format was created by Google in 2008). This binary data format is really light and fast, so it’s preferred in settings where you value speed and you want to minimize bandwidth usage. gRPC is built on top of HTTP/2 so it’s ideal for bidirectional communication; the client can initiate a long-lived connection with the server, over which RPC calls can continuously be sent.
Why are there two different downloads?
As of v1.20.0, the protobuf
module . The protoc-gen-go
plugin that is packed with this module is for the protocol buffer compiler to generate Go code. But in order to generate the Go bindings for gRPC service definitions you need to download the protoc-gen-go-grpc
plugin as well.
We’ll be building a basic unary service with a server and client components. The idea is to implement a SayHello method where a client sends a request with their name, and the server responds with a greeting including the name. We need to define our service and methods, generate the protobuf
files, and we’ll be ready to play with our code!
Make a new directory called grpc_noon
and create a subfolder called proto
. Navigate to proto
and make 2 subfolders called server
and client
respectively. Your folder should now look like the image below:
Within the proto
subfolder, create a file called greeter.proto
.
Navigate to the proto
folder and open up a terminal. Enter the following code:
go mod init example.com/grpc-greeter
Let’s quickly go over what we just did. In order to track dependencies when creating a module, we use go mod init
but normally, a module path will include its origin in the form of a repo URL. In cases where we might publish modules for others to use, this is a common practice. For us, example.com
is simply a placeholder URL that is used internally to serve as an accurate pointer to the module. If you’d like, you can use a local path instead such as ./grpc-greeter
.
syntax = "proto3"; // specify the syntax
package greeter; // declare the greeter package
option go_package = "example.com/grpc-greeter;grpc_greeter"; // direct the go package to our module path
service Greeter { // define our service and the rpc methods we'd like to call
rpc SayHello(Message) returns (MessageResponse) {}
}
message Message { // define the request with expected parameters
string name = 1;
}
message MessageResponse { // define the response with expected parameters
string greeting = 1;
}
The option
is defined to help with the next step but otherwise, we are simply defining the expected interaction we want between our client and server.
Now that the service definition is complete, we can generate the necessary protobuf
files.
From the proto
folder, run the following command:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./greeter.proto
For this step, we’re using the plugins we downloaded earlier to generate our proto
files in specific paths. The .
simply refers to the current directory, so we’re dictating the output directory locations. At this point, your folder should look like this (left sidebar):
If you take a look at the generated files, you’ll see that the server and client implementations are in the greeter_gRPC_pb.go
file whereas the serialization and de-serialization logic for the defined service are in the greeter.pb.go
file.
Navigate to the server
subfolder.
Next, we’ll use the protobuf
file to create our server struct, and make the function that we’d like to call from the client.
Lastly, let’s start up the server within our main
function and set it to run on a specific port.
To confirm that it’s working, you can run go run greeter_server.go
and you should see that it’s up and running!
Navigate to the client
subfolder.
grpc
library methods to accomplish both.Greeter
client and make the RPC call with our SayHello
method (make sure to pass in a name!). We’ll use methods from the generated protobuf
files to do both.Greeting
field from the struct that we defined in our proto
file. A note - regardless of which case you use in your definitions, all protobuf
files will use uppercase for fields and method definitions.
On the client side, you’ll see a message logged that says:
2022/11/23 19:36:47 Response from server: Bob`