Building an API Gateway using Ocelot + gRPC — Part 2

Matheus Xavier
C# Programming
Published in
7 min readNov 16, 2023

--

In my last article I explained some concepts about the API Gateway in a simpler way, giving a theoretical basis about it, explaining some functions and their benefits. If you missed it, go there to read it and then come back here to continue with the practical part.

Now I will show you how to build your own API Gateway using gRPC + Ocelot.

Briefing

Before showing the code, let’s give a brief explanation, we will basically have two services in this sample:

  • Product Service → It will be responsible for managing our products, it will have information such as price, description and name.
  • Basket Service → It will be responsible for managing the products in a user’s basket.

The Basket Service contains only the Product Id, when searching for all the products that a user has in their basket it we will be necessary to get other product information such as price, name and description, this is where the Aggregator will come in, it will fetch the information from the user’s basket in the Basket Service, then we will search for information about these products in Product Service, aggregating all the necessary information for our client.

The aggregator will use gPRC to access the Basket and Product services, which I already mentioned and explained its entire concept in another article I wrote, if you missed it you can check it out here.

We will also have our Ocelot service which will be the only entry point for all requests, it will know which request it should forward to the Aggregator and which requests can go directly to the Basket or Product Service.

API Gateway (Ocelot + Aggregator) + Services

Show me the code

I will show some important points of implementing each service:

  • Product Service
  • Basket Service
  • Aggregator
  • Ocelot API Gateway

Product Service

Product Service is very simple, we basically have a ProductItem entity that contains basic product information:

  • Id
  • Name
  • Description
  • Price
  • Active

The Entity Framework was used in this service, an interesting point is that in Program.cs we have this code:

app.MigrateDbContext<ProductContext>((context, services) =>
{
IWebHostEnvironment env = services.GetRequiredService<IWebHostEnvironment>();
ILogger<ProductContextSeed> logger = services.GetRequiredService<ILogger<ProductContextSeed>>();

new ProductContextSeed()
.SeedAsync(context, env, logger)
.Wait();
});

Just by running the project it will apply all the necessary migrations, making the get started process simpler. Furthermore, after applying the migrations it also calls the SeedAsync method of ProductContextSeed which will already add some products to our database:

The Product Service has two entry points:

  • Controllers
  • gRPC Services

Whenever an endpoint is accessed by the Aggregator it will be developed within the gRPC services, whenever the endpoint is accessed directly by the client, that is, Ocelot will forward the request, we will create this endpoint in the controller.

Products Controller

In the case of the controller we only have one which is the ProductsController, in it we also have only one endpoint which is the endpoint that gets all products:

When the request that searches for all products arrives at the API Gateway, it will forward the request directly to this endpoint since this information is not dependent on any other service.

Product Service

In the case of gRPC service we have two methods:

  • DoesProductExist → It receives the id of a product and returns a result indicating whether this product exists or not
  • GetProductByIds → It receives a list of product ids and returns information about all of them

You can also check the contracts through the product.proto file:

These are the most important points of our Product Service, you can check the entire Product Service implementation on github.

Basket Service

The Basket Service is very simple and save information about customer basket in Redis with the following structure:

  • Customer Id
  • Items which is a list where inside it contains the Product Id and the Product Quantity

Following the same Product Service pattern we have two entry points:

  • Controllers
  • gRPC Services

Basket Controller

We only have one controller which is the BasketController, inside it we have only one endpoint that is responsible for removing a product from the customer basket:

The logic is very simple, we try to find a basket for the customer, if we don’t find it it means there is no need to remove the item and we simply return an Ok, otherwise we remove all products that have the Id that was passed and update the basket in redis.

Basket Service

In gRPC service there are two methods:

  • GetBasket → It receives the customer’s Id and returns all items in their basket.
  • AddBasketItem → It adds a new product to the customer’s basket.

You can also check the contracts through the basket.proto file:

These are the most important points of our Basket Service, you can check the entire Basket Service implementation on github.

API Gateway

The API Gateway is composed of two projects:

  • Ocelot API Gateway
  • Aggregator

Now let’s check each implementation.

Aggregator

The aggregator project has one controller which is the BasketController and two services that are responsible for accessing the Product and Basket Services.

Basket Controller

The BasketController has two endpoints:

  • GetBasketByIdAsync → It receives a customer’s id and returns all the products that are in that customer’s basket. Firstly, it accesses the Basket Service to get the customer’s basket, if there is no product an empty basket is returned, if there are some products then we access the Product Service to get additional information about these products such as Name, Description and Price.
  • AddBasketItemAsync → It receives the id of a customer, the product id and the product quantity, first it accesses the Product Service to ensure that the product id that was passed exists, if it exists, a request is made to the Basket Service to add this product in the customer’s basket.

Basket Service

The basket service is responsible for calling the Basket Service through gRPC calls, in this case, we have two calls:

  • AddProductToBasketAsync
  • GetBasketItemsAsync

Both methods are very simple and there is no need for an explanation, they basically receive some information and return it.

Product Service

The product service is responsible for calling the Product Service through gRPC calls, in this case, we have two calls:

  • DoesProductExistAsync
  • GetProductByIdsAsync

Both methods are very simple and there is no need for an explanation, they basically receive some information and return it.

These are the most important points of our Aggregator, you can check the entire Aggregator implementation on github.

Ocelot API Gateway

The OcelotApiGateway is extremely simple, it only has one class which is Program.cs:

There are two important points:

Configuring Ocelot

Obviously the first thing to do is add the Ocelot nuget package:

<PackageReference Include="Ocelot" Version="19.0.2" />

Then to configure Ocelot we add it:

builder.Services.AddOcelot();

After that you need to add the configuration json file:

builder.Configuration.AddJsonFile("ocelot.json");

And finally, just indicate that we will use it:

await app.UseOcelot();

Done! Our Ocelot is already configured.

Configuring Swagger For Ocelot

This part is not mandatory, but thinking about a scenario with several services, with some requests going to the aggregator, others to the basket service and some to the product service, swagger can help us make this more visible, documented and easier to test.

It is necessary adding two nuget packages:

<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9" />
<PackageReference Include="MMLib.SwaggerForOcelot" Version="7.0.0" />

Then we add the Swagger For Ocelot

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerForOcelot(builder.Configuration);

And configure the Swagger For Ocelot UI

app.UseSwaggerForOcelotUI(option =>
{
option.PathToSwaggerGenerator = "/swagger/docs";
});

Ocelot.json

Now the Ocelot is already configured and ready to use, the last step now is to configure the routes, for this Ocelot uses a json file which in our case is ocelot.json, in the end it looks like this:

Explaining a little about the configuration of each route:

  • /api/{version}/products → When receiving a request with this route, we will forward it to localhost:7087, which is the address where the Product Service is running
  • /api/{version}/customers/{id}/baskets → When receiving a request with this route we will forward it to localhost:7067 which is the address that the Aggregator is running at, it is worth highlighting that in this case we have configured both GET and POST, served both to get the current information from the a customer’s basket or to add a product to that basket.
  • /api/{version}/customers/{id}/baskets/{productId} → When receiving a request with this route, we will forward it to localhost:7298, which is the address where the Basket Service is running.

Another important point is the SwaggerEndPoints section, which is where we also configure the swagger for each of the services, it is worth noting that each of them has a key and that each endpoint that we register has a property called SwaggerKey to indicate which swagger is that route.

There are the most important points of our Ocelot API Gateway, you can check the entire implementation on github.

Conclusion

This was a simple example of how we can build our own API Gateway using a combination of Ocelot + gRPC, I ended up not delving into the whole gRPC implementation part because I already did it in this post, in the same way I also didn’t delve into the theoretical part because I had already done it in this post.

Github project with full code sample.

Thank you for reading and feel free to contact me if you have any questions.

--

--