Basket microservice(購物車微服務)主要用於處理購物車的業務邏輯,包括:redis
如上圖所示,本微服務採用數據驅動的CRUD微服務架構,來執行購物車商品的維護操做。並使用Redis數據庫進行持久化。
這種類型的服務在單個 ASP.NET Core Web API 項目中便可實現全部功能,該項目包括數據模型類、業務邏輯類及其數據訪問類。其項目結構以下:
數據庫
核心技術選型:json
Newtonsoft.Jsonapi
該微服務的核心領域實體是購物車,其類圖以下:安全
其中CustomerBasket
與BasketItem
爲一對多關係,使用倉儲模式進行持久化。架構
CustomerBasket
對象進行json格式的序列化和反序列化來完成在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); });
在本服務中主要須要處理如下事件的發佈和消費:app
private void ConfigureEventBus(IApplicationBuilder app) { var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>(); eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); }
以上都是基於事件總線來達成。async
購物車管理界面是須要認證和受權。那天然須要與上游的Identity Microservice
進行銜接。在啓動類進行認證中間件的配置。ide
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 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();
BasketDomainException
異常和HttpGlobalExceptionFilter
過濾器來實現的。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界面進行測試,那麼咱們就必須爲其集成認證受權。代碼以下:
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>(); });
其中有主要作了三件事:
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存儲。