Mai

Dans cet article nous allons expliquer comment utiliser la librairie Ocelot pour créer une passerelle vers nos microservices en créant une application simulant une petite boutique E-commerce.

Prérequis

  • Visual Studio 2022
  • .NET 6 SDK

 

Tout commença avec une histoire de microservices

 Les microservices sont un style d’architecture utilisé par de nombreuses organisations pour le développement de logiciels. Les avantages de cette approche incluent : 

  • Une plus grande scalabilité
  • Une meilleure flexibilité
  • Une plus grande résilience des applications

 

Cependant, la mise en place d’une architecture de microservices peut être complexe et nécessite souvent des outils spécialisés pour gérer les nombreux aspects de cette approche. 

Dans cette approche l’application est décomposée en plusieurs microservices autonomes et interopérables. Chaque service est responsable d’une fonction spécifique et communique avec les autres microservices via des protocoles standardisés tels que HTTP ou TCP. 

Lorsqu’un client a besoin de consommer plusieurs services, la configuration d’un endpoint distinct pour chaque service et sa gestion peuvent s’avérer difficiles. Par exemple, une application de commerce électronique peut fournir des services tels que la recherche des produits, l’affichage des avis clients, l’ajout des produits dans le panier, le paiement et l’historique des commandes…

Pour répondre à ce besoin, la solution consiste à placer une passerelle devant un ensemble d’applications, de façon à ce que l’application cliente n’ait besoin de connaître et de communiquer qu’avec un seul point de terminaison.

Les clients n’ont pas nécessairement besoin d’être mis à jour si les services sont fusionnés ou décomposés, seul le routage change, ce pattern est appelé API Gateway Pattern.

L’exemple ci-dessous montre l’architecture que nous allons mettre en place pour utiliser une API Gateway dans un projet E-Commerce :

La gestion et l’orchestration des requêtes est le rôle de l’API Gateway, c’est elle qui gère l’accès aux microservices. Ce pattern permet d’orchestrer les requêtes des clients vers les applications backend et de fournir un point d’entrée unique pour certains groupes de microservices. Cependant ce modèle présente des limites…

Lorsqu’un client veut consulter sa commande, la requête passe d’abord par l’API Gateway, puis se redirige vers le microservice des commandes pour charger sa liste des commandes. 

Ensuite, l’API Gateway lance un deuxième appel au microservice des produits pour récupérer les informations sur les produits achetés, puis agréger ces deux données (commandes et produits) et les renvoyer à l’interface graphique.

Imaginons maintenant que vous ayez besoin de créer une application mobile pour votre boutique et de proposer la même fonctionnalité via cette application.

Dans l’interface graphique desktop, nous pouvons afficher plus d’informations sur les produits, l’historique des commandes…car nous avons plus d’espace sur l’écran. A contrario, dans l’interface mobile, l’espace d’affichage est plus restreint et nous ne pouvons afficher que le titre des produits sans description et proposer un bouton qui affiche l’historique des commandes.

Pour résoudre cela, deux solutions sont possibles :

  1. Passer par la même API Gateway qui remonte toutes les informations puis ne sélectionner que les informations à afficher ;
  2. Créer une nouvelle API Gateway qui ne remonte cette fois que les informations demandées par l’interface graphique mobile et, si le client veut consulter son historique des commandes ou la description d’un produit, un deuxième appel est fait pour ça.

La première solution n’est pas optimale car nous remontons toutes les données alors que toutes ne seront pas consommées. Cela va induire une dégradation des performances, ce qui peut être préjudiciable pour le service proposé.

Par conséquent, c’est la deuxième solution que nous allons choisir. Nous récupérons uniquement les informations demandées par l’interface mobile, ceci afin de rendre les appels légers et rapides.

Et cette architecture a un nom, oui, c’est la BFF (Backend For Frontend) :

Une BFF, de quoi s’agit-il ?

BFF (Backend For Frontend) est un modèle d’architecture qui consiste à créer un backend dédié pour chaque application frontend (Web ou Mobile). Le backend est conçu pour répondre aux besoins spécifiques de l’application frontend, plutôt que de fournir un accès générique à l’ensemble des services. Cette approche permet de mieux contrôler les données exposées au frontend et de fournir une expérience utilisateur plus fluide.

Pourquoi utiliser Ocelot ?

Ocelot est une passerelle API .NET. Ce projet s’adresse aux applications utilisant .NET,  structurées en architecture microservices et qui ont besoin d’un point d’entrée unifié dans leur système. Cette librairie peut être utilisée pour créer une BFF pour une application frontend. Ocelot fournit des fonctionnalités telles que le routage, l’authentification, l’autorisation, le limiteur de débit, le caching, la transformation des en-têtes et bien plus encore.

Comment implémenter cette solution ?

Pour démarrer et à l’aide de Visual Studio, créez une solution vide nommée “MicroservicesApi” :

Puis un dossier nommé “Microservices” à la racine de la solution. Ce dossier contiendra quatre projets de types “ASP.NET Core Web API”, nommés “Microservice.Authentication”, “Microservice.Customers”, “Microservice.Products”, “Microservice.Orders”

Maintenant, il faut ajouter les opérations CRUD (Create, Read, Update et Delete) pour nos microservices  « Microservice.Customers », « Microservice.Products », « Microservice.Orders » et implémenter l’authentification pour « Microservice.Authentication ».

Note : Puisque notre intérêt principal est de vous montrer les différentes fonctionnalités d’Ocelot, on ne va pas montrer comment implémenter les opérations CRUD ou l’authentification pour les microservices précédents.

Note : Veuillez-vous assurer que les microservices utilisent des ports différents. Pour cela, il faut ouvrir le fichier “launchSettings.json” de chaque microservice et le modifier s’il y a redondance :

Pour implémenter notre “Gateway.Api”, on doit installer le package Ocelot à l’aide de la commande Install-Package Ocelot ou par Nuget packages manager :

Note : Vérifiez que vous êtes dans le projet “Gateway.Api” avant d’installer le package.

Ajoutez un nouveau fichier json appelé “ocelot.json” dans la racine de notre application. Ce fichier va contenir notre configuration ocelot.

Le code suivant doit être collé dans le fichier de configuration :

{
   "GlobalConfiguration": {},
   "Routes": [  {} ]
}

Aussi, dans la balise « GlobalConfiguration » il faut déclarer le BaseUrl :

"GlobalConfiguration": {
"BaseUrl": "https://localhost:7286"
}

Note : Le même port (7286) que celui que se trouve dans le fichier « LaunchSettings.json » doit être mis.

Dans le fichier Program.cs il faut mettre la configuration Ocelot, pour cela, les lignes suivantes doivent être ajoutées :

// Add ocelot configuration file and injecting ocelot service
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
builder.Services.AddOcelot();


var app = builder.Build();


await app.UseOcelot();
	
app.Run();

Nous avons indiqué que le fichier de config « ocelot.json » est nécessaire et qu’il faut recharger la configuration si on modifie le fichier qui porte cette dernière.

Ocelot nous permet de définir les différents chemins que peuvent emprunter les requêtes et les rediriger vers les bons services en fonction de critères tels que le chemin d’URL, le verbe de la requête HTTP…

Pour répondre à notre premier besoin de récupération des informations des produits, nous allons ajouter les lignes suivantes dans le fichier de config « ocelot.json », plus précisément dans « Routes » pour rendre Ocelot capable de connaître le routage vers le micro service des produits :


    {
      "UpstreamPathTemplate": "/gateway/products",
      "UpstreamHttpMethod": [ "Get" ],
      "DownstreamPathTemplate": "/api/products",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 7200
        }
      ]
    }
  1. UpstreamPathTemplate : Définit l’URL de la passerelle API qui reçoit les requêtes, puis redirige vers l’API du microservice.
  2. UpstreamHttpMethod : Définit les méthodes ou les verbes HTTP (GET, PUT, POST, PATCH, etc.) que la passerelle API utilise pour distinguer les requêtes.
  3. DownstreamPathTemplate : Définit l’URL vers laquelle une demande sera transmise, en d’autres termes, elle prend la requête du UpstreamPathTemplate et la redirige vers DownstreamPathTemplate.
  4. DownstreamScheme : Représente le protocole de communication avec le microservice.
  5. DownstreamHostAndPorts : Définit l’URL et le port des microservices qui vont recevoir les requêtes, dans notre cas le microservice « Microservice.Products ».

 

Avant de tester notre application, nous allons modifier la configuration d’exécution pour que tous les services soient lancés en même temps.

Pour faire cela, un simple clic droit sur notre solution et puis cliquez sur « Set startup projects… » et sur « Multiple startup projects » et mettez l’action de tous les services sur Start.

Après le lancement de l’application, 5 fenêtres CMD s’ouvrent.

Afin de récupérer les informations sur tous les produits, une requête GET vers l’url https://localhost:7286/gateway/products est envoyée.

Bingo ! Nous avons récupéré toutes les informations de nos produits.

Comment puis-je récupérer les informations sur un seul produit ?

Pour préciser l’identifiant de mon produit, il suffit de modifier la configuration ocelot de la façon suivante :

    {
       "UpstreamPathTemplate": "/gateway/products/{id}",
      "UpstreamHttpMethod": [ "Get", "Put", "Delete" ],
      "DownstreamPathTemplate": "/api/products/{id}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 7200
        }
      ]
    }

Notez que nous avons modifié UpstreamPathTemplate et DownstreamPathTemplate en ajoutant “/{id}” et en précisant les méthodes qui nécessitent un id (GET, PUT, DELETE)  

Nous allons essayer cette fois en cherchant un produit par son identifiant :

YES ! Nous avons les informations de notre produit et nous avons répondu à notre premier besoin 😊.

Comment ajouter de nouveaux produits ?

Pas de panique, il suffit seulement d’ajouter le verbe « Post » dans « UpstreamHttpMethod ». Ocelot va ensuite être capable de rediriger la requête de création d’un nouveau produit vers la route POST du microservice des produits :

    {
...
      "UpstreamHttpMethod": [ "Get", "Post" ],
...    }

Pour les autres routes, on répète les mêmes étapes en changeant les noms des routes.

Tout ça c’est bien mais comment limiter les appels à mes microservices ?

Pas de crainte, la librairie Ocelot permet de limiter le nombre d’appels à vos microservices grâce à la fonctionnalité Rate Limit.

Dans certains cas, les entreprises ont besoin de proposer un service payant. A titre d’exemple, la consultation des produits VIP est gratuite jusqu’à 3 fois par jour. Au-delà de cette limite, le service devient payant. Ocelot permet de gérer ces cas via une seule configuration.

Ainsi, pour ajouter une limite de débit, il faut ajouter les lignes suivantes dans la route souhaitée dans le fichier de config :

"RateLimitOptions": {
        "ClientWhitelist": [],
        "EnableRateLimiting": true,
        "Period": "1s",
        "PeriodTimespan": 1,
        "Limit": 1
      }
  • ClientWhitelist – Il s’agit d’un tableau qui contient la liste blanche du client. Cela signifie que le client figurant dans ce tableau ne sera pas impacté par la limitation de débit. 
  • EnableRateLimiting – Cette valeur spécifie l’activation de la limitation de débit.
  • Period – Cette valeur spécifie la période pendant laquelle la limite s’applique, telle que 1s, 5m, 1h, 1j, et ainsi de suite. Si vous effectuez plus de demandes pendant la période déterminée par la limite, vous devez attendre que PeriodTimespan s’écoule avant de faire une autre demande.
  • PeriodTimespan – Cette valeur spécifie que nous pouvons réessayer après un certain nombre de secondes.
  • Limit – Cette valeur spécifie le nombre maximum de demandes qu’un client peut effectuer sur une période définie.

 

Par la configuration ci-dessus, si on essaye de faire plus d’une requête par seconde on recevra une erreur.

Et notre service devient payant maintenant 😊.

Pour modifier le message et le code d’erreur on peut ajouter les lignes suivantes dans “GlobalConfiguration” :

"RateLimitOptions": {
      "QuotaExceededMessage": "Vous avez dépassé le nombre maximum des appels. Merci d’acheter le service",
      "HttpStatusCode": 429
    }

Le Rate Limit peut être utilisé pour d’autres raisons, il permet notamment de :

  • Améliorer la disponibilité : en limitant le débit, le rate limiting permet de prévenir les surcharges du système et de garantir une disponibilité constante.
  • Protéger le système : le rate limiting permet de prévenir les attaques par déni de service (DDOS) en limitant le nombre de requêtes que peut envoyer un utilisateur ou une application.
  • Garantir une qualité de service : en limitant le débit, le Rate Limiting permet de garantir une qualité de service équitable pour tous les utilisateurs et applications.
  • Économiser les ressources : le rate limiting permet d’économiser les ressources du système en limitant le nombre de requêtes et le volume de données traitées.

Puis-je le limiter via cette fonctionnalité pour éviter que mon client qui consulte beaucoup sa commande ne dégrade les performances ?

Non, votre client a le droit de consulter sa commande quand il veut. Pour ne pas dégrader les performances de vos microservices, Ocelot vous propose de mettre en place un système de cache.

Le caching est une technique de stockage temporaire des données les plus souvent utilisées pour les rendre rapidement accessibles et améliorer les performances globales du système. Les données peuvent être stockées en cache sur la mémoire vive, sur disque rapide ou sur un service de cache distribué comme Redis.

Et le cache, c’est compliqué de le mettre en place ?

Pas vraiment, Ocelot est conçu pour rendre ces services facilement administrables. Via la configuration Ocelot, vous pouvez gérer votre cache.

Pour ajouter le caching à notre application, il faut installer le package suivant dans le projet “Gateway.Api” :   

Install-Package Ocelot.Cache.CacheManager

Puis modifier le fichier Program.cs en changeant la ligne :

builder.Services.AddOcelot(builder.Configuration); 

par

builder.Services.AddOcelot(builder.Configuration); 

Enfin, on ajoute les lignes suivantes dans la route où on veut activer le caching :

 "FileCacheOptions": {
        "TtlSeconds": 15,
        "Region": "somename"
      }

Ici, « TtlSeconds » est la durée de cache, dans cet exemple le cache expirera après 15 secondes.

Et voilà, tout est prêt pour vous, les requêtes passent par le cache maintenant avant d’arriver dans vos microservices.

C’est génial, mais comment puis-je sécuriser les accès dans Ocelot ?

Avant de mettre en place cette fonctionnalité, il faut distinguer deux notions, l’authentification et l’autorisation.

Ce sont deux processus distincts qui sont souvent utilisés ensemble pour contrôler l’accès aux systèmes informatiques, aux réseaux et aux applications. L’authentification est le processus de vérification de l’identité d’un utilisateur, d’un appareil ou d’une application. Cela implique généralement de fournir des informations d’identification telles qu’un nom d’utilisateur et un mot de passe ou une reconnaissance faciale. 

L’authentification est la première étape pour accorder l’accès à une ressource et elle garantit que seules les entités autorisées peuvent y accéder. L’autorisation est le processus permettant de déterminer le contenu auquel un utilisateur ou une application est autorisé à accéder après avoir été authentifié. 

Cela implique de vérifier les autorisations et les privilèges de l’utilisateur pour déterminer s’il dispose des droits nécessaires pour effectuer une action particulière ou accéder à une ressource particulière.

Commençons par l’authentification : pour l’ajouter à notre BFF, nous devons tout d’abord ajouter une route dans notre configuration « ocelot.json » pour l’authentification.

Il faut aussi ajouter les lignes suivantes dans la balise « Routes » :

{
"UpstreamPathTemplate": "/gateway/login",
"UpstreamHttpMethod": [ "POST"],
"DownstreamPathTemplate": "/api/authentication/login",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7183
}
]
}

Note : N’oubliez pas de mettre le port correct (le port de microservice « Authentication.Api » )

Les lignes suivantes doivent être ajoutées dans le fichier « Program.cs » : 

//Adding authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection("AppSettings:SecretKey").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
});

Note : Le SecretKey qui se trouve dans appSettings de Gateway.Api doit être le même que celui de microservice « Authentication.Api ».

Puis on ajoute les lignes suivantes dans « Program.cs » pour activer l’authentification et l’autorisation :

app.UseAuthentication();
app.UseAuthorization();

Enfin, pour sécuriser une route, on doit ajouter les lignes suivantes dans la route souhaitée dans le fichier « ocelot.json »:

"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": []
}

Maintenant si on essaie de faire une requête vers le microservice des produits, nous recevons un code statut 401 Unauthorized :

 

Et voilà, tout fonctionne comme il faut !

Pour être authentifié, on doit entrer le nom d’utilisateur et le mot de passe et nous connecter à l’aide de la route que l’on a créée (/gateway/login).

Puis on copie le token pour l’utiliser dans la route protégée :

Très bien, nous avons mis en place notre système d’authentification 😊.

Et comment ajouter l’autorisation ?

Pour ajouter l’autorisation à notre route, on doit ajouter les lignes suivantes dans notre route dans le fichier « ocelot.json » :

 "RouteClaimsRequirement": {
"Role" :  "Admin"
}

Si on essaie d’envoyer la requête lorsque notre utilisateur a le rôle de « Customer », on recevra un code status 403 Forbidden, cela veut dire que seuls les admins sont autorisés à utiliser cette route.

Nickel ! 😊

Et comment puis-je agréger les données qui viennent de plusieurs microservices ?

L’agrégation de réponse est une technique utilisée pour fusionner les réponses de plusieurs services en un seul objet. Les passerelles API (API Gateways) le permettent en acceptant une seule demande des clients et en émettant plusieurs demandes parallèles aux services. 

Une fois que tous les services ont répondu, les passerelles API effectuent la fusion des données en un seul objet et le servent aux clients.

L’agrégation de réponse présente plusieurs avantages, notamment :

  • Réduction du nombre de requêtes : en agrégeant les réponses de plusieurs services en un seul objet, les passerelles API peuvent réduire le nombre de requêtes envoyées aux services en aval, ce qui peut améliorer les performances globales du système.
  • Amélioration de la disponibilité : en parallélisant les requêtes envoyées aux services en aval, les passerelles API peuvent améliorer la disponibilité du système, car une panne de l’un des services en aval n’affectera pas la disponibilité globale du système.
  • Réduction de la latence : en agrégeant les réponses de plusieurs services en un seul objet, les passerelles API peuvent réduire la latence globale de la réponse, car les temps de latence de chaque service en aval sont amortis.

 

Pour ajouter l’agrégation à notre application, nous devons identifier les routes qu’on souhaite agréger par une clé.

« Key »: « Products » dans la route products et « Key »: « Customers » dans la route customers dans le fichier de configuration « ocelot.json ». 

Ensuite, utilisez ces clés pour agréger vos données, nous ajoutons les lignes suivantes dans la racine de notre configuration (après la clé « Routes ») :


"Aggregates": [
{
"RouteKeys": [
"Products",
"Customers"
],
"UpstreamPathTemplate": "/gateway/products-and-customers"
}
]

Ici « RoutesKeys » contient les clés des routes qu’on veut agréger. « UpstreamPathTemplate » contient le lien ou la nouvelle route avec laquelle on va recevoir la nouvelle réponse agrégée.

Voici un exemple de la réponse. On voit que la réponse contient les données de « products » et « customers » aussi. 

Cool, nous avons maintenant toutes les informations dont nous avons besoin pour notre interface graphique 😊.

Pour conclure

La bibliothèque Microsoft Ocelot BFF est un outil performant qui simplifie la gestion des APIs dans une architecture de microservices. Elle est conçue pour fonctionner avec .NET Core et d’autres frameworks de développement web modernes. En résumé, Microsoft Ocelot BFF est un outil utile pour les développeurs qui créent des applications basées sur des microservices. 

La facilité d’utilisation et la séparation des rôles d’agrégation et d’orchestration sont ses principaux avantages pour ceux qui cherchent à simplifier la gestion des API dans un système distribué. De plus, sa compatibilité avec .NET Core et d’autres frameworks de développement Web modernes en fait un ajout à la boîte à outils de chaque développeur.

Pour aller plus loin

Dans cet article, nous avons vu les grandes fonctionnalités proposées par la librairie Ocelot, mais pour aller plus loin, vous pouvez consulter la documentation officielle sur ce lien : https://ocelot.readthedocs.io/en/latest/introduction/gettingstarted.html

 

Yassine CHAHID

Related Posts

Leave A Comment