is this framework better than REST API?

Hey! I’m Roman Makhnyk, .NET developer at NIX. For the fourth year I have been developing commercial projects, and now I design various applications based on cloud solutions.
In my article, I will describe the gRPC framework for the API. It is quite fresh, but has already established itself as a promising solution. The essence of gRPC is the most simplified communication of services. It is also important that data in gRPC is sent over a constant channel without serialization and endpoint routes.
If you are just getting started with the API or want to better understand gRPC, this article is for you.
REST and gRPC: key differences
To better understand the fundamental difference between these frameworks, let’s take a look at how they work. For REST to work through the most common CRUD approach, it is usually used five types HTTP requests for data of some resource:
-
GET: receive / read;
-
POST: add / write;
-
PUT / PATCH: update / change;
-
DELETE: delete.
The network interaction model looks like this: the client and the server are connected by a common network, at the right time the client sends an HTTP request, the server processes it and returns an HTTP response. While the server is doing its job, the client allocates an additional thread, in which the wait takes place.
If we change the HTTP requests to RPC calls (remote procedure calls), we get a similar scheme. The difference is how the server sends and receives a request and how the client receives a response.
RPC is a remote procedure call protocol that one program can use to call a method, function, or procedure on another that is available on the network. In this case, you do not need to thoroughly understand the network. As you can see in the diagram, the client and the server have an additional level of communication – client and server stubs. They are designed to send data to the correct address. Accordingly, in the code itself, we do not worry about this at all. These stubs are generated automatically. All we have to do is call the required procedure.
The new and, perhaps, the most effective and promising implementation of this concept is the gRPC framework. Compared to other RPC implementations, it has many advantages:
-
Idiomatic client libraries in over 10 languages… We can use libraries in Java, C #, JavaScript, Python, etc. It all looks native – not like we are using some kind of alien technology.
-
Simple structure for defining services… For this, .proto files are used.
-
HTTP / 2… Bidirectional HTTP / 2 based streaming
-
Tracing… It allows you to monitor procedure calls, which is very useful for debugging an application and analyzing the operation of services for their further optimizations and improvements.
-
Health check… With this mechanism, you can quickly check if the service is healthy and ready to process requests. It helps for load balancing.
-
Load balancing… This feature allows you to distribute the load across multiple instances of the server application, thereby greatly simplifying the issue of scaling. Balancing can be done both on the client side (for example, the client application alternately sends requests to different servers), and on an intermediate (special proxy) using a service mesh.
-
Connect authentication… Its absence has been a major flaw in past implementations of the protocol. Because of this vulnerability, RPC was only recommended for internal communication.
Comparing Google’s technology with REST, we also find our advantages here:
-
Working with Protobuf… REST uses JSON text format to transfer data and is not compressed. Protobuf is a binary format. Using it, we avoid transferring unnecessary data and we will not need to deserialize the received messages after that.
-
Handling HTTP requests… In the case of REST, you need to constantly think about what status code can come, what data will be stored, etc. In gRPC, we put a minimum of effort into calling RPCs and defining them.
-
Ease of defining contracts… In REST, to describe interfaces and documentation, you need to use third-party tools and libraries, such as OpenAPI or Swagger. GRPC is simple to define contracts in .proto files.
-
HTTP / 2… REST often uses an older version of this protocol, HTTP / 1.1.
Why is HTTP / 2 good? Among the important advantages:
-
binary data transfer format (reduces the size of messages and speeds up the work);
-
saving traffic (improved compression of HTTP messages, primarily headers);
-
the ability to transfer data streams;
-
multiplexing (in HTTP 1.1, to transfer three files, you need to establish three connections, in each of which a specific file will be requested and sent. In HTTP / 2, you can transfer everything over one connection);
-
stream prioritization.
How to set up a gRPC connection
The sequence for creating a gRPC channel includes several stages:
-
Opening sockets.
-
Establishing a TCP connection.
-
TLS negotiation.
-
Launching an HTTP / 2 connection.
-
Making a call to a gRPC procedure.
We could avoid the first four points by establishing a connection once and just using it – this is called Persistent Connection. Why not work with such a solution all the time? However, the question arises, how to set up load balancing when, say, we need several instances of a service – how to distribute them? There are several options:
-
Client side load balancing… In this case, the client library is aware of the existence of multiple instances of this service. For example, it will send requests to each of them as a percentage.
-
Load balancing through a proxy… If these services are hosted on some kind of orchestrator (such as Kubernetes), it may decide that there are too many instances, and removes one or, conversely, adds a new one. In this case, load balancing through the Service Mesh proxy helps. This could be Linkerd, Istio, Consul, etc. The client will establish one permanent connection to the Mesh, and he will already see what service instances there are, when they appear or disappear, and will handle it. The connection will only be to the actual services, and the client will not know about it – he always has one connection.
GRPC is sometimes compared to WCF. I don’t think this is relevant, since gRPC is a highly focused framework that does one thing well. WCF is a more general framework that supports RPC but also supports REST, SOAP, etc. Unfortunately, WCF is not universal in terms of supported platforms, because it is still strongly tied to .NET. In turn, gRPC can work in any environment, and it can be written in any languages from the list of supported.
However, gRPC is currently not fully functional in browsers. It is impossible to implement HTTP / 2 communication in the browser, because there is no such control over the communication channel, which can be, for example, in a .NET application. Therefore, Google suggests an alternative: use a gRPC proxy. That is, the browser itself will send HTTP 1.1 requests to the proxy, which will map the message to a gRPC procedure call.
In the picture above, you can see an example of how a huge number of Netflix microservices are tied together in this way – there are over 500 of them here! It is one of those companies that have benefited from the transition to the gRPC protocol and improved the performance of their services. Previously, they had to establish a separate connection for each request. It took milliseconds, but in the scale of such a company, downtime for hundreds of connections gradually adds up to seconds and minutes. Now all requests can be sent over one connection. The data transfer speed has increased significantly, because the very first four stages of establishing a connection are avoided.
How to create a gRPC application
And now the promised bonus – let’s try to create a simple Hello gRPC application.
First of all, I would like to note that Visual Studio already has a predefined template for creating such services. We just need to go to “Create a new project”, write “gRPC” – and we will see a template of the default gRPC application. At the same time, the two most current versions of .NET are supported: 5 and 3.1.
We have a .proto file that describes the service and interface. We see that this service has a certain SayHello procedure that accepts the Hello object in the request described below and returns it to the replay, which is described in the same file. A unique option for .NET is csharp_namespace
, necessary in order to indicate where to generate those classes that will be used in this application.
syntax = “proto3” ;
option csharp_namespace = “Grpcservice2” ;
package greet ;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) ;
}
// The request message containing the user’s name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
Under each property of sent and received messages there is a numbering of properties. This is necessary to understand in what order the data will be transmitted (since we still have a binary format), as well as to support previous versions. If we have updated our .proto file on the server and added a new property or removed the old one, then the client, on which they have not yet had time to update the file, will simply ignore the new property and read the old one – as empty, which was deleted. Accordingly, there will be no errors.
syntax = “proto3” ;
option csharp_namespace = “GrpcService2” ;
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) ;
}
// The request message containing the user’s name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
Now let’s look at registering gRPC services in a .NET application. To do this, you need one line that will add all the necessary services to our project.
using System. Collections.Generic ;
using System.Linq;
using System. Threading. Tasks ;
namespace GrpcService2
{
1 reference
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the cont
// For more information on how to configure your application, visit https://go.microsoft
0 reference
public void ConfigureServices (IServiceCollection services)
{
services. AddGrpc ()
}
// This method gets called by the runtime. Use this method to configure the HTTP reque
0reference
public void Configure (IApplicationBuilder app, IWebHostEnvironment env)
{
if (env. IsDevelopment ())
{
Below we see that gRPC services are mapped in the same way as regular controllers. Therefore, there is a ready-made Gtreeter service, the interface of which is defined in the .proto file. If we try to look at the code for this GreeterService, we will see that there is already a default implementation. The service itself is inherited from a certain GreeterBase – and this is exactly the file into which various information and classes related to sending and receiving messages are generated. All we do is inherit from this already created object and implement the functions as we need them.
// This method gets called by the runtime. Use this method to configure the HTTP reque
0 reference
public void Configure (IApplicationBuilder app, IWebHostEnvironment env)
{
if (env. IsDevelopment ())
app. UseDeveloperExceptionPage();
}
app. UseRouting ();
app. UseEndpoints (endpoints =>
{
endpoints MapGrpcService<GreeteService>();
endpoints. MapGet ( “ / ”, async context =>);
{
await context. Response. WriteAsyns (“Communication with gRPC endpoints must
{ );
} );
}
}
}
As for creating a client for this service, I propose to go to the already created one. Here we see that the WebApi client is an application that will accept HTTP requests and call the gRPC service to perform a procedure on it. All that has changed in the .proto file is the namespace, into which the client code will be generated.
syntax = “proto3”;
option csharp_namespace = “WebApi”;
package greet;
service Greeter {
prc SayHello (HelloRequest) returns (HelloReply) ;
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string name = 1;
}
In the project itself, Protobuf files are registered separately. That is, not as a regular file, but it is indicated that a specific Protobuf and the role of this service in this contract will be used. In this case, the role will be client-side. In this case, in the server project, we indicate that the same .proto file is used, but the role is the server.
<Project Sdk = “Microsoft.NET.Sdk.Web”>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Protobuf Include = “Protosgreet.photo” GrpcServices=”Client” />
</ItemGroup>
</ItemGroup>
<PackageReference Include= “Grpc. AspNetCore” Version= “2.39.0” />
<PackageReference Include= “Grpc. Net. ClientFactory” Version= “2.39.0” />
<PackageReference Include= “Swashbuncle. AspNetCore” Version= “5.6.3” />
</ItemGroup>
</Project>
In order to reuse the same connection, we register a gRPC client factory. This is done simply: we give AddGrpcClient, indicate which service needs the client, and add the address at which the service itself will be hosted (not this one, but the gRPC server; the client will be separate).
1 reference
public IConfiguration Configuration { get; }
0reference
public void ConfigureServices (IServiceCollection services)
{
services. AddGrpcClient<Greeter. GreeterClient>(0 =>
o.Address = new Uri ( “https://localhost :500 “) ;
} ) ;
services. AddControllers () ;
services. AddSwaggerGen(c =>
{
c.SwaggerDoc ( “v1”, new OpenApiInfo { Title = “WebApi”, Version = “v1” } ) ;
} )
}
0 references
public void Configure (IApplicationbuilder app, IWebhostEnvironment env)
Now let’s try using our gRPC client. We can pull it out at any time in the object we need through Dependency Injection. Then – to refer to it as to an ordinary object. In our case, I made a SayHello endpoint. This is a controller function, so it will accept HTTP requests and will turn to the gRPC service for processing.
{Route ( “ [controller] “ ) ]
1reference
public class GreetController : СontrollerBase
{
private readonly Greeter. GreeterClients _client;
0references
public GreetController (Greeter. GreeterClient client)
{
client = client;
}
[HttpPost]
0references
public asyns Task <IActionResult> SayHello ([FromBody] HelloRequest request)
{
var result = await _client. SayHello (request) ;
return Ok ( new { MessageSum = result. Message ] );
}
}
}
Next, we launch the gRPC service, make sure that it is functional, and launch the client. Let’s use Swagger to send this request to make the procedure a little easier. And we will specify some name as an argument. The gRPC service saw the call to this procedure, processed it, logged it, and in the response we received the expected result – and that’s all.
Nowadays, quite a few projects use gRPC, although, unfortunately, the community of its fans is not as large as that of the REST API. The technology is relatively new, and many are not yet used to it. But I’m sure that with such a rapid rise in the popularity of gRPC, the developer community will grow more actively in recent years. After all, as you can see, gRPC really offers many advantages in solving complex problems.