Programing and Development

The best API protocol. REST vs gRPC

gRPCvsREST

The best API protocol. REST vs gRPC

gRPC, REST’s up-and-coming competitor, approaches synchronous communication from another angle, offering protocol buffers and typed contracts. What does that mean for your project?

 

In today’s technology landscape, most projects require the use of APIs. APIs bridge communication between services that may represent a single, complex system but may also reside on separate machines or use multiple, incompatible networks or languages.

Many standard technologies address the interservice communication needs of distributed systems, such as REST, SOAP, GraphQL, or gRPC. While REST is a favored approach, gRPC is a worthy contender, offering high performance, typed contracts, and excellent tooling.

REST Overview

Representational state transfer (REST) is a means of retrieving or manipulating a service’s data. A REST API is generally built on the HTTP protocol, using a URI to select a resource and an HTTP verb (e.g., GET, PUT, POST) to select the desired operation. Request and response bodies contain data that is specific to the operation, while their headers provide metadata. To illustrate, let’s look at a simplified example of retrieving a product via a REST API.

Here, we request a product resource with an ID of 11 and direct the API to respond in JSON format:

GET /products/11 HTTP/1.1
Accept: application/json

Given this request, our response (irrelevant headers omitted) may look like:

HTTP/1.1 200 OK
Content-Type: application/json

{ id: 11, name: "Purple Bowtie", sku: "purbow", price: { amount: 100, currencyCode: "USD"  }  }

While JSON may be human-readable, it is not optimal when used between services. The repetitive nature of referencing property names—even when compressed—can lead to bloated messages. Let’s look at an alternative to address this concern.

gRPC Overview

gRPC Remote Procedure Call (gRPC) is an open-source, contract-based, cross-platform communication protocol that simplifies and manages interservice communication by exposing a set of functions to external clients.

Built on top of HTTP/2, gRPC leverages features such as bidirectional streaming and built-in Transport Layer Security (TLS). gRPC enables more efficient communication through serialized binary payloads. It uses protocol buffers by default as its mechanism for structured data serialization, similar to REST’s use of JSON.

Unlike JSON, however, protocol buffers are more than a serialized format. They include three other major parts:

  • A contract definition language found in .proto files (We’ll follow proto3, the latest protocol buffer language specification.)
  • Generated accessor-function code
  • Language-specific runtime libraries

The remote functions that are available on a service (defined in a .proto file) are listed inside the service node in the protocol buffer file. As developers, we get to define these functions and their parameters using protocol buffers’ rich type system. This system supports various numeric and date types, lists, dictionaries, and nullables to define our input and output messages.

 

These service definitions need to be available to both the server and the client. Unfortunately, there is no default mechanism to share these definitions aside from providing direct access to the .proto file itself.

This example .proto file defines a function to return a product entry, given an ID:

syntax = "proto3";

package product;

service ProductCatalog {
    rpc GetProductDetails (ProductDetailsRequest) returns (ProductDetailsReply);
}

message ProductDetailsRequest {
    int32 id = 1;
}

message ProductDetailsReply {
    int32 id = 1;
    string name = 2;
    string sku = 3;
    Price price = 4;
}

message Price {
    float amount = 1;
    string currencyCode = 2;
}
Snippet 1: ProductCatalog Service Definition

The strict typing and field ordering of proto3 make message deserialization considerably less taxing than parsing JSON.

Comparing REST vs. gRPC

To recap, the most significant points when comparing REST vs. gRPC are:

 RESTgRPC
Cross-platformYesYes
Message FormatCustom but generally JSON or XMLProtocol buffers
Message Payload SizeMedium/LargeSmall
Processing ComplexityHigher (text parsing)Lower (well-defined binary structure)
Browser SupportYes (native)Yes (via gRPC-Web)

Where less-strict contracts and frequent additions to the payload are expected, JSON and REST are great fits. When contracts tend to stay more static and speed is of the utmost importance, gRPC generally wins out. In most projects I have worked on, gRPC has proved to be lighter and more performant than REST.

gRPC Service Implementation

Let’s build a streamlined project to explore how simple it is to adopt gRPC.

Creating the API Project

To get started, we will create a .NET 6 project in Visual Studio 2022 Community Edition (VS). We will select the ASP.NET Core gRPC Service template and name both the project (we’ll use InventoryAPI) and our first solution within it (Inventory).

 

Un cuadro de diálogo "Configure su nuevo proyecto" dentro de Visual Studio 2022. En esta pantalla, escribimos "InventoryAPI" en el campo Nombre del proyecto, seleccionamos "C:\MyInventoryService" en el campo Ubicación y escribimos "Inventario" en el campo Nombre de la solución . Dejamos "Colocar la solución y el proyecto en el mismo directorio" sin marcar.

 

Now, let’s choose the .NET 6.0 (Long-term support) option for our framework:

 

Un cuadro de diálogo de información adicional dentro de Visual Studio 2022. En esta pantalla, seleccionamos ".NET 6.0 (soporte a largo plazo)" en el menú desplegable Framework. Dejamos "Habilitar Docker" sin marcar.

 

Defining Our Product Service

Now that we’ve created the project, VS displays a sample gRPC prototype definition service named Greeter. We will repurpose Greeter’s core files to suit our needs.

  • To create our contract, we will replace the contents of greet.proto with Snippet 1, renaming the file product.proto.
  • To create our service, we will replace the contents of the GreeterService.cs file with Snippet 2, renaming the file ProductCatalogService.cs.
using Grpc.Core;
using Product;

namespace InventoryAPI.Services
{
    public class ProductCatalogService : ProductCatalog.ProductCatalogBase
    {
        public override Task<ProductDetailsReply> GetProductDetails(
            ProductDetailsRequest request, ServerCallContext context)
        {
            return Task.FromResult(new ProductDetailsReply
            {
                Id = request.Id,
                Name = "Purple Bowtie",
                Sku = "purbow",
                Price = new Price
                {
                    Amount = 100,
                    CurrencyCode = "USD"
                }
            });
        }
    }
}
Snippet 2: ProductCatalogService

The service now returns a hardcoded product. To make the service work, we need only change the service registration in Program.cs to reference the new service name. In our case, we will rename app.MapGrpcService<GreeterService>(); to app.MapGrpcService<ProductCatalogService>(); to make our new API runnable.

Fair Warning: Not Your Standard Protocol Test

While we may be tempted to try it, we cannot test our gRPC service through a browser aimed at its endpoint. If we were to attempt this, we would receive an error message indicating that communication with gRPC endpoints must be made through a gRPC client.

Creating the Client

To test our service, let’s use VS’s basic Console App template and create a gRPC client to call the API. I named mine InventoryApp.

For expediency, let’s reference a relative file path by which we will share our contract. We will add the reference manually to the .csproj file. Then, we’ll update the path and set Client mode. Note: I recommend you become familiar with and have confidence in your local folder structure before using relative referencing.

Here are the .proto references, as they appear in both the service and client project files:

Service Project File
(Code to copy to client project file)
Client Project File
(After pasting and editing)
  <ItemGroup>
    <Content Update="Protos\product.proto" GrpcServices="Server" />
  </ItemGroup>
  <ItemGroup>
    <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" />
  </ItemGroup>

Now, to call our service, we’ll replace the contents of Program.cs. Our code will accomplish a number of objectives:

  1. Create a channel that represents the location of the service endpoint (the port may vary, so consult the launchsettings.json file for the actual value).
  2. Create the client object.
  3. Construct a simple request.
  4. Send the request.
using System.Text.Json;
using Grpc.Net.Client;
using Product;

var channel = GrpcChannel.ForAddress("https://localhost:7200");
var client = new ProductCatalog.ProductCatalogClient(channel);

var request = new ProductDetailsRequest
{
    Id = 1
};

var response = await client.GetProductDetailsAsync(request);

Console.WriteLine(JsonSerializer.Serialize(response, new JsonSerializerOptions
{
    WriteIndented = true
}));
Console.ReadKey();
Snippet 3: New Program.cs

Preparing for Launch

To test our code, in VS, we’ll right-click the solution and choose Set Startup Projects. In the Solution Property Pages dialog, we’ll:

  • Select the radio button beside Multiple startup projects, and in the Action drop-down menu, set both projects (InventoryAPI and InventoryApp) to Start.
  • Click OK.

Now we can start the solution by clicking Start in the VS toolbar (or by pressing the F5 key). Two new console windows will display: one to tell us the service is listening, the other to show us details of the retrieved product.

gRPC Contract Sharing

Now let’s use another method to connect the gRPC client to our service’s definition. The most client-accessible contract-sharing solution is to make our definitions available through a URL. Other options are either very brittle (file shared through a path) or require more effort (contract shared through a native package). Sharing through a URL (as SOAP and Swagger/OpenAPI do) is flexible and requires less code.

To get started, make the .proto file available as static content. We will update our code manually because the UI on the build action is set to “Protobuf Compiler.” This change directs the compiler to copy the .proto file so it may be served from a web address. If this setting were changed through the VS UI, the build would break. Our first step, then, is to add Snippet 4 to the InventoryAPI.csproj file:

  <ItemGroup>
    <Content Update="Protos\product.proto">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" />
  </ItemGroup>
Snippet 4: Code to Add to the InventoryAPI Service Project File

Next, we insert the code in Snippet 5 at the top of the ProductCatalogService.cs file to set up an endpoint to return our .proto file:

using System.Net.Mime;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
Snippet 5: Namespace Imports

And now, we add Snippet 6 just before app.Run(), also in the ProductCatalogService.cs file:

var provider = new FileExtensionContentTypeProvider();
provider.Mappings.Clear();
provider.Mappings[".proto"] = MediaTypeNames.Text.Plain;
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Protos")),
    RequestPath = "/proto",
    ContentTypeProvider = provider
});

app.UseRouting();
Snippet 6: Code to Make .proto Files Accessible Through the API

With Snippets 4-6 added, the contents of the .proto file should be visible in the browser.

A New Test Client

Now we want to create a new console client that we will connect to our existing server with VS’s Dependency Wizard. The issue is that this wizard doesn’t talk HTTP/2. Therefore, we need to adjust our server to talk over HTTP/1 and start the server. With our server now making its .proto file available, we can build a new test client that hooks into our server via the gRPC wizard.

  1. To change our server to talk over HTTP/1, we’ll edit our appsettings.json JSON file:
    1. Adjust the Protocol field (found at the path Kestrel.EndpointDefaults.Protocols) to read Https.
    2. Save the file.
  2. For our new client to read this proto information, the server must be running. Originally, we started both the previous client and our server from VS’s Set Startup Projects dialog. Adjust the server solution to start only the server project, then start the solution. (Now that we have modified the HTTP version, our old client can no longer communicate with the server.)
  3. Next, create the new test client. Launch another instance of VS. We’ll repeat the steps as detailed in the Creating the API Project section, but this time, we’ll choose the Console App template. We’ll name our project and solution InventoryAppConnected.
  4. With the client chassis created, we’ll connect to our gRPC server. Expand the new project in the VS Solution Explorer.
    1. Right-click Dependencies and, in the context menu, select Manage Connected Services.
    2. On the Connected Services tab, click Add a service reference and choose gRPC.
    3. In the Add Service Reference dialog, choose the URL option and input the http version of the service address (remember to grab the randomly generated port number from launchsettings.json).
    4. Click Finish to add a service reference that can be easily maintained.

Feel free to check your work against the sample code for this example. Since, under the hood, VS has generated the same client we used in our first round of testing, we can reuse the contents of the Program.cs file from the previous service verbatim.

When we change a contract, we need to modify our client gRPC definition to match the updated .proto definition. To do so, we need only access VS’s Connected Services and refresh the relevant service entry. Now, our gRPC project is complete, and it’s easy to keep our service and client in sync.

 

Start learning today in Bootcamp AI

 

Subscribe to our newsletter 

Sign up to be the first to get it.

Leave your thought here

Your email address will not be published.