Demystifying gRPC: A Beginner’s Guide to High-Performance API Communication

Kerem Gümüş
Arvato Tech
Published in
6 min readNov 16, 2023

Introduction

Efficient API communication is a critical aspect of modern software development for several reasons like performance, scalability, bandwidth and resource usage.

  • Performance: In today’s distributed and microservice-based architectures, software systems often rely on multiple services communicating over the network. Inefficient communication can lead to increased latency, slower response times and decreased all overall system performance.
  • Scalability: As software systems grow, the number of API calls and the volume of data exchanged can increase significantly. Efficient API communication is crucial for maintaining system performance and scalability a the load on the grows.
  • Bandwidth and Resource Usage: Inefficient communication can lead to unnecessary use of network bandwidth and server resources. This can result in higher infrastructure costs and suboptimal resource utilization.

Why Choose gRPC ?

workflow

gRPC is a high-performance and efficient remote procedure call (RPC) framework developed by Google. Allows us to use another service or a method on remote server as if it were the method on of our own service. Offers easy, secure and fast communication in the client-server relationship.

  • Protocol Buffers (ProtoBuf): gRPC uses Protocol Buffers as its interface definition language for defining service contracts and message structures. ProtoBuf is a compact and highly efficient binary serialization format that reduces both message size and parsing overhead.
  • HTTP/2: It uses HTTP/2 as its transport protocol, which is desgined for low latency and efficient multiplexing. This enables multiple requests and responses to be multiplexed over a single connection, reducing overhead and improving performance.
  • Bidirectional Streaming: gRPC supports bidirectional streaming, allowing both the client and server to send a stream of messages to each other over a single connection. This i a particularly useful for real-time communication and interactive applications.
  • Code Generation: gRPC generates client and server code in various programming languages based on the service definition, which ensures type safety and reduces the likelihood of API-related errors.

Example gRPC Usage

Suppose we have two services called Discount and Basket. Our discount service can be considered as a service we have created to manage discounts for products. Our basket service is a service where we can add, remove or update products before purchasing.

When adding a product to our basket, we need to check whether there is a discount defined for this product. For this, we have to communicate with our Discount service.

basket and discount service relation

As seen above, gRPC contains microservices in the adding products to the basket step of a project. The gRPC IDLs to be used in the communication methods and models Discount service are defined on “discount.proto”. We have to define the methods and models we will use in the gRPC integration on the proto file. We need to define this “discount.proto” file on both client and server side. The proto file defines the structure of the messages and gRPC services used in communication. Both services need to agree on this contract to communicate effectively. They generate code based on this shared contract to serialize/deserialize messages and handle gRPC calls.

Since gRPC is programming language independent, we can use different languages for the services, but in this example we will only see our Discount service developed with .NET.

gRPC Implementation with .NET

  • First of all, we create a new gRPC project on Visual Studio.
  • After the project is created, you can see the file structure. Let’s open the “Protos” folder in this section. You can examine the “greet.proto” file if you want, then delete this file.
  • Now let’s create our “discount.proto” file and specify our model and methods.
project folder structure
  • We specified the methods, message types and their field.
syntax = "proto3";

option csharp_namespace = "MyGrpcService.Protos";

service DiscountProtoService {
// Methods
rpc GetDiscount (GetDiscountRequest) returns (DiscountModel);
rpc CreateDiscount (CreateDiscountRequest) returns (DiscountModel);
rpc UpdateDiscount (UpdateDiscountRequest) returns (UpdateDiscountResponse);
rpc DeleteDiscount (DeleteDiscountRequest) returns (DeleteDiscountResponse);
}

message GetDiscountRequest {
string productName = 1;
}

message DiscountModel {
int32 id = 1;
string productName = 2;
string description = 3;
int32 amount = 4;
}

message CreateDiscountRequest {
DiscountModel discount = 1;
}

message UpdateDiscountRequest {
DiscountModel discount = 1;
}

message DeleteDiscountRequest {
string productName = 1;
}

message DeleteDiscountResponse {
bool success = 1;
}

message UpdateDiscountResponse {
bool success = 1;
}
  • In order for our gRPC code to be generated automatically, we edit the properties of our “discount.proto” file as follows.
  • Now when we build the project, this code will be generated automatically on the back side. After the build, if you want to examine it, we can see the generated file as follows by clicking the “Show all files” button.
  • Here is an example service that implements this concept.
using Grpc.Core;
using MyGrpcService.Protos;
using MyGrpcService.Repositories;

namespace MyGrpcService.Services;

public class DiscountService(IDiscountRepository _repository, ILogger<DiscountService> _logger) : DiscountProtoService.DiscountProtoServiceBase
{
public override async Task<DiscountModel> GetDiscount(GetDiscountRequest request, ServerCallContext context)
{
var discount = await _repository.GetDiscountAsync(request.ProductName);

if (discount == null)
{
throw new RpcException(new Status(StatusCode.NotFound, $"Discount with ProductName={request.ProductName} is not found."));
}

_logger.LogInformation($"Discount is retrieved for ProductName: {discount.ProductName}, Amount: {discount.Amount}");

return discount;
}

public override async Task<DiscountModel> CreateDiscount(CreateDiscountRequest request, ServerCallContext context)
{
var discount = await _repository.GetDiscountAsync(request.Discount.ProductName);

if (discount != null)
{
throw new RpcException(new Status(StatusCode.AlreadyExists, $"Discount with ProductName={request.Discount.ProductName} already exists."));
}

await _repository.CreateDiscountAsync(request.Discount);
_logger.LogInformation($"Discount is successfully created. ProductName : {request.Discount.ProductName}");

return request.Discount;
}

public override async Task<UpdateDiscountResponse> UpdateDiscount(UpdateDiscountRequest request, ServerCallContext context)
{
var discount = await _repository.GetDiscountAsync(request.Discount.ProductName);

if (discount == null)
{
throw new RpcException(new Status(StatusCode.NotFound, $"Discount with ProductName={request.Discount.ProductName} is not found."));
}

var updated = await _repository.UpdateDiscountAsync(request.Discount);
_logger.LogInformation($"Discount is successfully updated. ProductName : {request.Discount.ProductName}");

return new UpdateDiscountResponse { Success = updated };
}

public override async Task<DeleteDiscountResponse> DeleteDiscount(DeleteDiscountRequest request, ServerCallContext context)
{
var discount = await _repository.GetDiscountAsync(request.ProductName);

if (discount == null)
{
throw new RpcException(new Status(StatusCode.NotFound, $"Discount with ProductName={request.ProductName} is not found."));
}

var deleted = await _repository.DeleteDiscountAsync(request.ProductName);
_logger.LogInformation($"Discount is successfully deleted. ProductName: {request.ProductName}");

return new DeleteDiscountResponse { Success = deleted };
}
}

In order not to extend the article further, I will show examples via postman. Through Postman, you can select the proto file you have developed and the methods will be drawn automatically you have defined directly to postman.

postman example
console

Conclusion

In this guide, we’ve delved into the world of gRPC and uncovering a robust solution for high-performance API communication.

Key Takeaways

Efficiency and Performance: gRPC, powered by HTTP/2, redefines efficiency in API communication, boasting multiplexing, header compression and asynchronous streaming for lightning-fast interactions.

Protocol Buffers for Service Definition: Protocol Buffers offer a language-agnostic way to define services and messages, ensuring strong typing, consistency and ease of use across platform.

Automatic Code Generation: The ability of gRPC to generate client and server code simplifies development, reducing boilerplate code and accelerating the implementation process.

In wrapping up, gRPC emerges as a game-changer in the realm of API communication, seamlessly integrating with .NET 8 to offer unparalleled speed, scalability and versatility. Its ability to define services through Protocol Buffers, automate code generation and harness the efficiency of HTTP/2 highlights its significance in modern software development.

I hope you enjoyed reading this article. I wish everyone a pleasant day :)

References

LinkedIn | Kerem Gümüş

--

--