I heard some colleagues talking about gRPC framework and its benefits and advantages regarding Rest, so I dig a little in the gRPC world and its concepts to learn new ways to develop an API.
What is gRPC?
gRPC is an open-source API architecture and system. It’s based on the Remote Procedure Call (RPC) model. While the RPC model is broad, gRPC is a specific implementation.
gRPC is a system that implements traditional RPC with several optimizations. For instance, gRPC uses Protocol Buffers and HTTP 2 for data transmission. It also abstracts the data exchange mechanism from the developer.
gRPC vs REST
The following image compares the basics of REST APIs and gRPC:
Implementation
To better learn the gRPC concepts, I created a simple chat room scenario.
Project Setup
We'll create two projects within a solution:
- ChatAppServer: The gRPC server that handles chat room logic.
- ChatAppClient: The client application that users will run to join chat rooms and send messages (This is a console App project).
Then we'll install the following nuget packages:
- Grpc.AspNetCore
- Grpc.Tools
- Grpc.AspNetCore.Server.ClientFactory (this one only on the ChatAppClient project)
Proto file
We'll need to create a '.proto' file to define the gRPC service. This file specifies the service methods and message types.
This is my chat.proto file:
syntax = "proto3";
option csharp_namespace = "ChatApp";
package chat;
service ChatService {
rpc JoinRoom (JoinRoomRequest) returns (JoinRoomResponse);
rpc SendMessage (SendMessageRequest) returns (SendMessageResponse);
rpc ReceiveMessages (ReceiveMessagesRequest) returns (stream ChatMessage);
}
message JoinRoomRequest {
string username = 1;
string room = 2;
}
message JoinRoomResponse {
bool success = 1;
}
message SendMessageRequest {
string username = 1;
string room = 2;
string message = 3;
}
message SendMessageResponse {
bool success = 1;
}
message ReceiveMessagesRequest {
string room = 1;
}
message ChatMessage {
string username = 1;
string message = 2;
int64 timestamp = 3;
}
We have to include the .proto file on the .csproj so we can enable the code generator, so on the ChatAppServer.csproj file add the following:
<ItemGroup>
<Protobuf Include="Protos\chat.proto" GrpcServices="Server" />
</ItemGroup>
And on the ChatAppClient.csproj add the following:
<ItemGroup>
<Protobuf Include="Protos\chat.proto" GrpcServices="Client" />
</ItemGroup>
After this, if we clean, restore and build the projects, the code will be generated and you can check it out on the path "obj\Debug\net6.0\Protos".
Implement the server
In the server project, implement the ChatService.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using ChatApp;
using Grpc.Core;
using Microsoft.Extensions.Logging;
namespace ChatAppServer.Services
{
public class ChatServiceImpl : ChatService.ChatServiceBase
{
private static readonly ConcurrentDictionary<string, List<IServerStreamWriter<ChatMessage>>> _rooms = new ConcurrentDictionary<string, List<IServerStreamWriter<ChatMessage>>>();
public override Task<JoinRoomResponse> JoinRoom(JoinRoomRequest request, ServerCallContext context)
{
if (!_rooms.ContainsKey(request.Room))
{
_rooms[request.Room] = new List<IServerStreamWriter<ChatMessage>>();
}
return Task.FromResult(new JoinRoomResponse { Success = true });
}
public override Task<SendMessageResponse> SendMessage(SendMessageRequest request, ServerCallContext context)
{
if (_rooms.TryGetValue(request.Room, out var clients))
{
var message = new ChatMessage
{
Username = request.Username,
Message = request.Message,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
foreach (var client in clients)
{
client.WriteAsync(message);
}
}
return Task.FromResult(new SendMessageResponse { Success = true });
}
public override async Task ReceiveMessages(ReceiveMessagesRequest request, IServerStreamWriter<ChatMessage> responseStream, ServerCallContext context)
{
if (_rooms.TryGetValue(request.Room, out var clients))
{
clients.Add(responseStream);
try
{
await Task.Delay(Timeout.Infinite, context.CancellationToken);
}
finally
{
clients.Remove(responseStream);
}
}
}
}
}
And we need to add the following on the Progra.cs file:
builder.Services.AddGrpc();
app.MapGrpcService<ChatServiceImpl>();
Implement the client
Modify the Program.cs file of the client project:
using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using ChatApp;
using Grpc.Core;
class Program
{
static async Task Main(string[] args)
{
// Create a channel to the gRPC server
var channel = GrpcChannel.ForAddress("https://localhost:7187");
var client = new ChatService.ChatServiceClient(channel);
Console.WriteLine("Enter your username:");
var username = Console.ReadLine();
Console.WriteLine("Enter the room you want to join:");
var room = Console.ReadLine();
// Join the specified room
var joinResponse = await client.JoinRoomAsync(new JoinRoomRequest
{
Username = username,
Room = room
});
if (joinResponse.Success)
{
Console.WriteLine($"Joined room: {room}");
}
else
{
Console.WriteLine("Failed to join room");
return;
}
// Task for receiving messages
var receiveTask = Task.Run(async () =>
{
using var call = client.ReceiveMessages(new ReceiveMessagesRequest { Room = room });
await foreach (var message in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"[{message.Username}] {message.Message}");
}
});
// Loop to send messages
while (true)
{
var message = Console.ReadLine();
if (message.ToLower() == "exit") break;
var sendMessageResponse = await client.SendMessageAsync(new SendMessageRequest
{
Username = username,
Room = room,
Message = message
});
if (!sendMessageResponse.Success)
{
Console.WriteLine("Failed to send message");
}
}
// Wait for the receiving task to complete
await receiveTask;
}
}
Running the application
Use the dotnet run command to run the projects.
Open 2 terminals of clients to interact with each other.
Conclusion
This is still a very simple case of implementing gRPC framework to develop an API, and I still have a lot to work on and learn about this, I'm very excited!
And that's it guys, I hope you liked it, stay tuned for more!
Top comments (0)