• eShopOnContainers 知多少[7]:Basket microservice


    购物车界面

    引言

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

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

    架构模式

    数据驱动/CRUD 微服务设计

    如上图所示,本微服务采用数据驱动的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<ConnectionMultiplexer>(sp =>
    {
        var settings = sp.GetRequiredService<IOptions<BasketSettings>>().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<IEventBus>();
    
        eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
        eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
    }
    

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

    认证和授权

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

    private void ConfigureAuthService(IServiceCollection services)
    {
        // prevent from mapping "sub" claim to 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;
            options.Audience = "basket";
        });
    }
    protected virtual void ConfigureAuth(IApplicationBuilder app)
    {
        if (Configuration.GetValue<bool>("UseLoadTest"))
        {
            app.UseMiddleware<ByPassAuthMiddleware>();
        }
        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<IActionResult> 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<string>("IdentityUrlExternal")}/connect/authorize",
            TokenUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
            Scopes = new Dictionary<string, string>()
            {
                { "basket", "Basket API" }
            }
        });
        options.OperationFilter<AuthorizeCheckOperationFilter>();
    });
    

    其中有主要做了三件事:

    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<AuthorizeAttribute>().Any() ||
                               context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any();
            if (hasAuthorize)
            {
                operation.Responses.Add("401", new Response { Description = "Unauthorized" });
                operation.Responses.Add("403", new Response { Description = "Forbidden" });
                operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
                operation.Security.Add(new Dictionary<string, IEnumerable<string>>
                {
                    { "oauth2", new [] { "basketapi" } }
                });
            }
        }
    }
    

    最后

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

  • 相关阅读:
    Redis操作命令大全
    Redis实用监控工具一览
    Redis缓存雪崩、缓存穿透、缓存击穿、缓存降级、缓存预热、缓存更新
    Redis GEO地理位置信息,查看附近的人
    详解redis持久化
    详解Supervisor进程守护监控
    详解Redis Cluster集群
    arduino使用rfid
    树莓派控制WS2812
    Arduino读取温湿度dh11+烟雾气体MQ2+彩灯ws2812
  • 原文地址:https://www.cnblogs.com/sheng-jie/p/10218839.html
Copyright © 2020-2023  润新知