欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

eShopOnContainers 知多少[7]:Basket microservice

发布时间:2023/12/4 60 豆豆
生活随笔 收集整理的这篇文章主要介绍了 eShopOnContainers 知多少[7]:Basket microservice 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

引言

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

  • 购物车商品的CRUD

  • 订阅商品价格更新事件,进行购物车商品同步处理

  • 购物车结算事件发布

  • 订阅订单成功创建事件,进行购物车的清空操作

  • 架构模式

    如上图所示,本微服务采用数据驱动的CRUD微服务架构,并使用Redis数据库进行持久化。 这种类型的服务在单个 ASP.NET Core Web API 项目中即可实现所有功能,该项目包括数据模型类、业务逻辑类及其数据访问类。其项目结构如下:

    核心技术选型:

  • ASP.NET Core Web API

  • Entity Framework Core

  • Redis

  • Swashbuckle(可选)

  • Autofac

  • Eventbus

  • Newtonsoft.Json

  • 实体建模和持久化

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

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

  • 通过对 CustomerBasket对象进行json格式的序列化和反序列化来完成在redis中的持久化和读取。

  • 以单例模式注入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);

  • });

  • 事件的注册和消费

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

  • 事件发布:当用户点击购物车结算时,发布用户结算事件。

  • 事件消费:订单创建成功后,进行购物车的清空

  • 事件消费:商品价格更新后,进行购物车相关商品的价格同步


  • private void ConfigureEventBus(IApplicationBuilder app)

  • {

  •    var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();


  •    eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();

  •    eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();

  • }

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

    认证和授权

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

  • 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存储。

    总结

    以上是生活随笔为你收集整理的eShopOnContainers 知多少[7]:Basket microservice的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。