eShopOnContainers 知多少[7]:Basket microservice
发布日期:2021-05-09 06:35:57 浏览次数:17 分类:博客文章

本文共 5934 字,大约阅读时间需要 19 分钟。

引言

Basket microservice(购物车微服务)主要用于处理购物车的业务逻辑,包括:

  1. 购物车商品的CRUD
  2. 订阅商品价格更新事件,进行购物车商品同步处理
  3. 购物车结算事件发布
  4. 订阅订单成功创建事件,进行购物车的清空操作

架构模式

如上图所示,本微服务采用数据驱动的CRUD微服务架构,来执行购物车商品的维护操作。并使用Redis数据库进行持久化。

这种类型的服务在单个 ASP.NET Core Web API 项目中即可实现所有功能,该项目包括数据模型类、业务逻辑类及其数据访问类。其项目结构如下:

核心技术选型:

  1. ASP.NET Core Web API
  2. Entity Framework Core
  3. Redis
  4. Swashbuckle(可选)
  5. Autofac
  6. Eventbus
  7. Newtonsoft.Json

实体建模和持久化

该微服务的核心领域实体是购物车,其类图如下:

其中CustomerBasketBasketItem为一对多关系,使用仓储模式进行持久化。

  1. 通过对CustomerBasket对象进行json格式的序列化和反序列化来完成在redis中的持久化和读取。
  2. 以单例模式注入redis连接ConnectionMultiplexer,该对象最终通过构造函数注入到RedisBasketRepository中。
services.AddSingleton
(sp =>{ var settings = sp.GetRequiredService
>().Value; var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); configuration.ResolveDns = true; return ConnectionMultiplexer.Connect(configuration);});

事件的注册和消费

在本服务中主要需要处理以下事件的发布和消费:

  1. 事件发布:当用户点击购物车结算时,发布用户结算事件。
  2. 事件消费:订单创建成功后,进行购物车的清空
  3. 事件消费:商品价格更新后,进行购物车相关商品的价格同步
private void ConfigureEventBus(IApplicationBuilder app){    var eventBus = app.ApplicationServices.GetRequiredService
(); eventBus.Subscribe
(); eventBus.Subscribe
();}

以上都是基于事件总线来达成。

认证和授权

购物车管理界面是需要认证和授权。那自然需要与上游的Identity Microservice进行衔接。在启动类进行认证中间件的配置。

private void ConfigureAuthService(IServiceCollection services){    // prevent from mapping "sub" claim to nameidentifier.    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();    var identityUrl = Configuration.GetValue
("IdentityUrl"); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.Authority = identityUrl; options.RequireHttpsMetadata = false; options.Audience = "basket"; });}protected virtual void ConfigureAuth(IApplicationBuilder app){ if (Configuration.GetValue
("UseLoadTest")) { app.UseMiddleware
(); } app.UseAuthentication();}

手动启用断路器

在该微服务中,定义了一个中断中间件FailingMiddleware,通过访问http://localhost:5103/failing获取该中间件的启用状态,通过请求参数指定:即通过http://localhost:5103/failing?enablehttp://localhost:5103/failing?disable来手动中断和恢复服务,来模拟断路,以便用于测试断路器模式。

开启断路后,当访问购物车页面时,Polly在重试指定次数依然无法访问服务时,就会抛出BrokenCircuitException异常,通过捕捉该异常告知用户稍后再试。

public class CartController : Controller{    //…    public async Task
Index() { try { var user = _appUserParser.Parse(HttpContext.User); //Http requests using the Typed Client (Service Agent) var vm = await _basketSvc.GetBasket(user); return View(vm); } catch (BrokenCircuitException) { // Catches error when Basket.api is in circuit-opened mode HandleBrokenCircuitException(); } return View(); } private void HandleBrokenCircuitException() { TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business message due to Circuit-Breaker)"; }}

注入过滤器

在配置MVC服务时指定了两个过滤器:全局异常过滤器和模型验证过滤器。

// Add framework services.services.AddMvc(options =>{    options.Filters.Add(typeof(HttpGlobalExceptionFilter));    options.Filters.Add(typeof(ValidateModelStateFilter));}).AddControllersAsServices();
  1. 全局异常过滤器是通过定义BasketDomainException异常和HttpGlobalExceptionFilter过滤器来实现的。
  2. 模型验证过滤器是通过继承ActionFilterAttribute特性实现的ValidateModelStateFilter来获取模型状态中的错误。
public class ValidateModelStateFilter : ActionFilterAttribute{    public override void OnActionExecuting(ActionExecutingContext context)    {        if (context.ModelState.IsValid)        {            return;        }        var validationErrors = context.ModelState            .Keys            .SelectMany(k => context.ModelState[k].Errors)            .Select(e => e.ErrorMessage)            .ToArray();        var json = new JsonErrorResponse        {            Messages = validationErrors        };        context.Result = new BadRequestObjectResult(json);    }}

SwaggerUI认证授权集成

因为默认启用了安全认证,所以为了方便在SwaggerUI界面进行测试,那么我们就必须为其集成认证授权。代码如下:

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"    });    options.AddSecurityDefinition("oauth2", new OAuth2Scheme    {        Type = "oauth2",        Flow = "implicit",        AuthorizationUrl = $"{Configuration.GetValue
("IdentityUrlExternal")}/connect/authorize", TokenUrl = $"{Configuration.GetValue
("IdentityUrlExternal")}/connect/token", Scopes = new Dictionary
() { { "basket", "Basket API" } } }); options.OperationFilter
();});

其中有主要做了三件事:

  1. 配置授权Url
  2. 配置TokenUrl
  3. 指定授权范围
  4. 注入授权检查过滤器AuthorizeCheckOperationFilter用于拦截需要授权的请求
public class AuthorizeCheckOperationFilter : IOperationFilter{    public void Apply(Operation operation, OperationFilterContext context)    {        // Check for authorize attribute        var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType
().Any() || context.ApiDescription.ActionAttributes().OfType
().Any(); if (hasAuthorize) { operation.Responses.Add("401", new Response { Description = "Unauthorized" }); operation.Responses.Add("403", new Response { Description = "Forbidden" }); operation.Security = new List
>>(); operation.Security.Add(new Dictionary
> { { "oauth2", new [] { "basketapi" } } }); } }}

最后

本服务较之前讲的Catalog microservice 而言,主要是多了一个认证和redis存储。

上一篇:MediatR 知多少
下一篇:.NET 反编译调试神器:dnSpy了解一下

发表评论

最新留言

感谢大佬
[***.8.128.20]2025年04月12日 17时40分23秒