Distributed Services using gRPC
Practical guide to gRPC and how to use it in .NET and Python
Efficient communication is often one of the main drivers for modern software systems, even in a microservice driven world. gRPC can deal with these requirements. In this article, we will look at some basics of gRPC and implement a first client and server application using .NET. Besides, a Python-based client demonstrates how easy efficient communication between different services can be.
What is gRPC?
gRPC is a protocol to communicate between different endpoints of a distributed system. It uses HTTP/2 and protocol buffers (protobuf). Like any other RPC system, gRPC consists of a server that defines methods and responses a client can call. Clients then implement the specific server stub and can consume provided methods like shown in the next figure for .NET and Python.
gRPC can use protobuf as interface definition language and as an exchange format of transferred messages. It supports many programming languages like Android Java, C#, C++, Dart, Go, Java, Kotlin, Node.js, Objective-C, PHP, Python and Ruby. Software Development Kit (SDK) often supports language-specific implementations. So based on gRPC, you can quickly implement distributed systems or microservices written in different languages, dependent upon the programming languages the developers’ preferences.
Protocol buffers
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data. (https://developers.google.com/protocol-buffers)
By default, gRPC uses protocol buffers (protobuf) for data exchange and interface definition language. Protocol buffers is an open-source mechanism for serializing structured data, developed by Google. In this section, we will look at how protobuf can be used as a data exchange format and as an interface definition language. Let’s get started with a simple file foo.proto
that defines the structure of data to serialize.
In the previous example, you can see that protobuf uses messages to structure data. Each of them contains a set of name-value pairs called fields. Hence, you can think of a message in protobuf like a kind of class or struct in object-oriented programming languages.
Now it’s time to compile the protobuf message definition to respective types in your preferred and supported programming language. Therefore, the protobuf compiler protoc
ist available for download on GitHub release pages.
For C# following command will generate classes that can then be used via protocol buffer API to send and receive messages.
In our example, the resulting C# code consists of:
- A class
FooReflection
that contains metadata about the protobuf message - A class
Foo
that implementsIMessage<Foo>
and provides the propertiesId
andDescription
.
Besides, class Foo
provides serialization and deserialization related methods. To send and receive a message in C# use the methods shown in the following example. We use a memory stream here, but it could be any other stream as well. Please note NuGet package Google.Protobuf is required to read and write to streams.
Further information about how protoc
generates C# code can be found here: https://developers.google.com/protocol-buffers/docs/reference/csharp-generated
According to gRPC, protobuf supports interface definition as well. protoc
is shipped with an additional plugin to generate client and server-side code. The following example shows a small sample of a gRPC interface definition.
In the previous example, the gRPC service FooService
is defined. This service provides the unary method GetFoo
. As you can see, request and response objects are defined as protobuf messages. Again protoc
can be used to generate the code in the preferred programming language.
For C#, it’s the best way to use Grpc.Tools NuGet package. We will have a look into C# tool integration later in this article. In the case of a gRPC interface definition and C# as programming language resulting code now consists of:
In general:
- Class ‘FooReflection’ that holds metadata about the protobuf request and response message.
- A class
FooRequest
that implementsIMessage<FooRequest>
and provides the properties for a gRPC request of methodGetFoo
. - A class
FooResponse
that implementsIMessage<FooResponse>
and provides the fields for a gRPC response of methodGetFoo
.
In the case of server-side code generation:
- A class
FooServiceBase
that acts as a base class for the specific service implementation.
In the case of client-side code generation:
- A class
FooServiceClient
that contains the stubs to interact with the server-side part via a gRPC channel.
Communication patterns
In the previous service definition, a unary method is used to interact between client and server. But there are even more ways how a gRPC client and server can interact.
Unary RPCs A client sends a request to the server and gets a single response back
rpc GetFoo (FooRequest) returns (FooResponse);
Server streaming RPCs A client sends a request to the server and gets a stream to read a sequence of messages. Within interface definition, it is declared by using an additional stream keyword before response.
rpc GetFoos(FooRequest) returns (stream FooResponse);
Client streaming RPCs A client writes a sequence of messages and sends them to the server. After finished writing, the client waits until the server has read them and returns the response. Within interface definition, it is declared by using an additional stream keyword before request.
rpc SendFoos(stream FooRequest) returns (FooResponse);
Bidirectional streaming RPCs Both sides send a sequence of messages using a read and a write stream. Both streams are independent. Within interface definition, it is declared by using an additional stream keyword before request and response.
rpc SendAndGetFoos(stream FooRequest) returns (stream FooResponse);
A connection in gRPC can be terminated by the server, the client, or triggered by timeouts. The client and the server make independent and local determinations about the state of a call. So a streaming call might succeed on the server but fails on the client due to a timeout. In case of a cancellation, any changes made by the client or server are not rolled back automatically. The connection terminates immediately. In detail, the behavior concerning connection and metadata handling is often language-specific.
A first server and client application in C
So let’s get started with our first full working gRPC client and server in .NET. Using Visual Studio, it’s easy to create both applications. Using the project wizard, you have to search for gRPC
and follow the assistant's steps, as shown in the following figure. Optionally you can enable Docker support, but that's not the focus of this article.
Visual Studio then creates a gRPC server project, and we are ready to go. By modifying the protobuf based interface definition and implementing custom business logic inside generated files, we can complete our gRPC server. But what’s happening behind the scenes?
However, Visual Studio creates the protobuf file along with a class for the server implementation. But there is happening even more. The *.csproj-file references our protobuf explicitly using a tag.
This entry tells Visual Studio to treat that file as input for gRPC client and server generation. The property GrpcServices
specifies which parts Visual Studio should generate.
The second important reference inside the *csproj-file is the NuGet package Grpc.AspNetCore.
Via this package Grpc.Tools will be also available in our solution. Grpc.Tools
provides the support to handle protobuf files using protoc compiler. With this basic setup, the linked protobuf file is compiled on every build or when triggered by the Run Custom Tool context menu item.
Since gRPC server applications are based on ASP.NET Core our project contains a Startup.cs
. Within this file gRPC support can be easily activated using AddGrpc
and MapGrpcService<T>
where T is our gRPC service implementation.
gRPC uses HTTP/2 as its transfer protocol. Therefore, Kestrel (Webserver used in ASP.NET Core) needs to be configured via appsettings.json
or in Program.cs
Following example advises all endpoints to use HTTP/2 as transfer protocol.
Alternatively, you can specify multiple endpoints, each using a specific configuration. For example, this setup can be used when hosting a REST API and a gRPC API in one project and you want the REST API to use HTTP/1 as transfer protocol.
Further, gRPC endpoints should be secured with Transport Layer Security (TLS). During development, an endpoint secured with TLS is automatically generated. Usually, you are prompted to trust the respective certificate but you can trigger the process using the command dotnet dev-certs https --trust
. In production, TLS must be configured via appsettings.json
or Program.cs
with your specific certificate parameters. Via property Certificate
all required parameters can be provided to Kestrel.
Now it’s time to build our first client application. Just create a new .NET Core console app and install the NuGet packages Google.Protobuf, Grpc.Net.Client and Grpc.Tools. Afterward, just add a link to the protobuf file you have already used to create the server application. The resulting *.csproj-file can look like this sample file.
Please note that property GrpcServices
of the <Protobuf/>
tag is set to value Client
. This advises the protoc compiler to create client stubs instead of the server related parts.
So let’s start creating our client. First, we must create a GrpcChannel
using the URI of our gRPC server application. In a second step, we can use this channel to setup our gRPC service client. Using this client we can call all defined methods. The following sample shows a unary RPC call. To use a gRPC client without TLS support in .NET Core 3.x, an additional step is required. The switch System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport
of our application context must be true
. Apps using .NET 5 framework don't need additional configuration, but to call insecure gRPC services they must use Grpc.Net.Client
version 2.32.0 or later.
When using a ASP.NET Core application gRPC client implementation is even easier using HttpClientFactory
integration. Therefore, just install Grpc.Net.ClientFactory package and register the gRPC service during startup:
The gRPC client type is registered transient within ASP.NET Core dependency injection mechanism. So the client can easily be injected and consumed in types created by dependency injection, e.g. controllers.
That’s all you have to know when you start building a first gRPC server and client using C#. Please see the code reference at the end of the article for a fully working example.
Python meets .NET
Since gRPC is available in a lot of programming languages, it enables communication between them. Let’s think about multiple microservices. One is developed using C#, one using Node.js, and another one using Python. And of course, all of them can integrate. In this section, we will focus on communication between .NET and Python. (Please note that the following samples require Python and pip installed on the respective system)
Before we can start writing our Python client we have to install gRPC related dependencies and generate Python stubs for our protobuf file. Following commands demonstrates the required steps:
The result of these steps contains two files:
foo_pb2.py
which contains generated request and response classesfoo_pb2_grpc.py
which contains generated client and server classes.
Our gRPC server is running based on ASP.NET Core and using TLS for secure communication, so we have to export the .NET development certificate first. Therefore, you have to open the Windows Certificate Manager using the command certmgr
. Within the certificate manager navigate to Current user > Personal > Certificates
and export the certificate having ASP.NET Core HTTPS development certificate
set as friendly name. Please use the following export options Without key, DER-coded X.509 (.cer)
. Python supports certificate using PEM-format by default. So the easiest way is to convert our certificate to this format. This can be done using the command openssl x509 -inform der -in localhost.cer -out localhost.pem
.
Finally, it’s time to have a look at a sample gRPC client implementation using Python. We are using the generated gRPC stubs and our certificate to set up our simple test client as shown in the next code snippet:
Now you can run the Python application using python client.py
. The script calls our ASP.NET Core based gRPC server using a secured communication channel and print the server response to the console.
Wrapping up
In this article, I walked you through the basics of gRPC and how it can be used using various programming languages. Based on this you can now build your own services that can communicate in a very efficient way. In .NET 5 there are further performance improvements concerning the gRPC stack. So there is no reason to build inter-service calls without the use of gRPC.
On GitHub, a full working sample application is available. It covers client and server applications to communication between different services written in different programming languages. (Example how to work with gRPC using .NET Core / ASP.NET Core and Python)
Thank you for taking the time to read my article. 😄
If you enjoyed it and want to see more coding-related content then follow me on my social profiles. Don't hesitate to like, comment or share this post. I appreciate any kind of feedback on this article.