
引言
Basket microservice(购物车微服务)主要用于处理购物车的业务逻辑,包括:
购物车商品的CRUD
订阅商品价格更新事件,进行购物车商品同步处理
购物车结算事件发布
订阅订单成功创建事件,进行购物车的清空操作
架构模式

如上图所示,本微服务采用数据驱动的CRUD微服务架构,并使用Redis数据库进行持久化。 这种类型的服务在单个 ASP.NET Core Web API 项目中即可实现所有功能,该项目包括数据模型类、业务逻辑类及其数据访问类。其项目结构如下:
核心技术选型:
ASP.NET Core Web API
Entity Framework Core
Redis
Swashbuckle(可选)
Autofac
Eventbus
Newtonsoft.Json
实体建模和持久化
该微服务的核心领域实体是购物车,其类图如下:
其中 CustomerBasket与 BasketItem为一对多关系,使用仓储模式进行持久化。
通过对
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?enable和 http://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 modeHandleBrokenCircuitException();}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 attributevar 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存储。
