Le microservice de panier est responsable de la gestion des opérations relatives au panier d'achats, notamment :
- CRUD (Création, Lecture, Mise à jour, Suppression) des articles dans le panier.
- Synchronisation des articles du panier avec les mises à jour de prix des produits via des événements.
- Publication d'événements lors de la finalisation du panier.
- Gestion de la suppression des articles du panier suite à la création réussie d'une commande.
Architecture et Tcehnologies
Ce microservice adopte une architecture CRUD pilotée par les données, implémentée au sein d'un projet ASP.NET Core Web API unique. La persistance des données est assurée par Redis.
Les principales technologies utilisées sont :
- ASP.NET Core Web API
- Entity Framework Core (pour la gestion du contexte de données, bien que Redis soit utilisé pour la persistance principale)
- Redis (pour le stockage des données du panier)
- Swashbuckle (pour la génération de documentation OpenAPI/Swagger)
- Autofac (pour l'injection de dépendances)
- Un Bus d'Événements (pour la communication inter-services via des événements)
- Newtonsoft.Json (pour la sérialisation/désérialisation JSON)
Modélisation et Persistance des Données
L'entité principale est CustomerBasket, qui entretient une relation un-à-pluiseurs avec BasketItem. Le stockage dans Redis s'effectue en sérialisant et désérialisant ces objets en format JSON.
La connexion à Redis est gérée via un singleton ConnectionMultiplexer, injecté dans le dépôt Redis (RedisBasketRepository).
services.AddSingleton<ConnectionMultiplexer>(sp =>
{
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
var configurationOptions = ConfigurationOptions.Parse(settings.ConnectionString, true);
configurationOptions.ResolveDns = true; // Résolution DNS activée
return ConnectionMultiplexer.Connect(configurationOptions);
});
Gestion des Événements
Le microservice gère la publication et la souscription aux événements suivants :
- Publication d'un événement de finalisation du panier lorsqu'un utilisateur procède au paiement.
- Souscription à un événement de création de commande réussie pour vider le panier de l'utilisateur.
- Souscription à un événement de changement de prix de produit pour mettre à jour les prix des articles correspondants dans le panier.
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
// S'abonner aux événements pertinents
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
}
Toutes ces interactions sont médiatisées par le bus d'événements.
Authentification et Autorisation
L'accès aux fonctionnalités de gestion du panier est protégé. L'intégration avec le microservice d'identité (Identity Microservice) est configurée au démarrage de l'application.
private void ConfigureAuthService(IServiceCollection services)
{
// Empêcher le mappage du claim "sub" vers NameIdentifier
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false; // Pour les environnements de développement
options.Audience = "basket"; // Audience du token JWT
});
}
protected virtual void ConfigureAuth(IApplicationBuilder app)
{
// Utilisation d'un middleware pour contourner l'authentification dans les scénarios de test de charge
if (Configuration.GetValue<bool>("UseLoadTest"))
{
app.UseMiddleware<ByPassAuthMiddleware>();
}
app.UseAuthentication(); // Activation du middleware d'authentification
}
Simulation de Disjoncteur (Circuit Breaker)
Un middleware personnalisé, FailingMiddleware, permet de simuler manuellement l'état "ouvert" du disjoncteur. En accédant à l'endpoint /failing avec les paramètres ?enable ou ?disable, on peut activer ou désactiver cette simulation.
Lorsque le disjoncteur est ouvert, les tentatives d'accès au service de panier échouent. Si des mécanismes de résilience comme Polly sont utilisés pour les appels HTTP, une BrokenCircuitException sera levée après un certain nombre d'échecs, permettant d'informer l'utilisateur.
public class CartController : Controller
{
// ... autres dépendances et méthodes ...
public async Task<IActionResult> Index()
{
try
{
var user = _appUserParser.Parse(HttpContext.User);
// Appel du client typé pour interagir avec le service de panier
var vm = await _basketSvc.GetBasket(user);
return View(vm);
}
catch (BrokenCircuitException)
{
// Gestion de l'exception lorsque le service de panier est indisponible (circuit ouvert)
HandleBrokenCircuitException();
}
return View();
}
private void HandleBrokenCircuitException()
{
TempData["BasketInoperativeMsg"] = "Le service de panier est actuellement indisponible. Veuillez réessayer plus tard.";
}
}
Filtres MVC
Deux filtres globaux sont configurés lors de la mise en place des services MVC : un filtre d'exception global et un filtre de validation de modèle.
// Ajout des services du framework
services.AddMvc(options =>
{
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); // Filtre d'exception global
options.Filters.Add(typeof(ValidateModelStateFilter)); // Filtre de validation de modèle
}).AddControllersAsServices();
- Le filtre d'exception global gère les exceptions personnalisées comme
BasketDomainExceptionvia le filtreHttpGlobalExceptionFilter. - Le filtre
ValidateModelStateFilter, hérité deActionFilterAttribute, vérifie la validité du modèle d'entrée et retourne des erreurs de validation si nécessaire.
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
{
return; // Le modèle est valide, continuer l'exécution
}
// Récupération des erreurs de validation
var validationErrors = context.ModelState
.Keys
.SelectMany(key => context.ModelState[key].Errors)
.Select(error => error.ErrorMessage)
.ToArray();
// Création d'une réponse d'erreur JSON
var jsonErrorResponse = new JsonErrorResponse
{
Messages = validationErrors
};
// Retourner une réponse BadRequest avec les détails des erreurs
context.Result = new BadRequestObjectResult(jsonErrorResponse);
}
}
Intégration de l'Authentification dans Swagger UI
Pour faciliter les tests via l'interface Swagger UI, l'authentification OAuth2 est intégrée.
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Info
{
Title = "Basket HTTP API",
Version = "v1",
Description = "The Basket Service HTTP API",
TermsOfService = "Terms Of Service"
});
// Configuration du schéma d'authentification OAuth2
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit", // ou "accessCode" selon le besoin
AuthorizationUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ "basket", "Basket API" } // Portée pour l'API du panier
}
});
// Application du filtre pour vérifier les autorisations nécessaires
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
Cette configuration inclut la définition des URLs d'autorisation et de token, ainsi que les portées requises. Le filtre AuthorizeCheckOperationFilter s'assure que les endpoints nécessitant une authentification sont correctement marqués.
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
// Vérifier la présence de l'attribut [Authorize] sur la classe ou la méthode
var hasAuthorizeAttribute = context.ApiDescription.ControllerAttributes().OfType<AuthorizeAttribute>().Any() ||
context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any();
if (hasAuthorizeAttribute)
{
operation.Responses.Add("401", new Response { Description = "Unauthorized" });
operation.Responses.Add("403", new Response { Description = "Forbidden" });
// Ajout de la sécurité OAuth2 à l'opération
operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
operation.Security.Add(new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new [] { "basketapi" } } // Spécification de la portée
});
}
}
}
Par rapport au microservice Catalog, le microservice Basket intègre des fonctionnalités d'authentification et utilise Redis pour la persistance des données.