eShopOnContainers 知多少[7]:Basket microservice
引言
Basket microservice(購物車微服務)主要用于處理購物車的業(yè)務邏輯,包括:
購物車商品的CRUD
訂閱商品價格更新事件,進行購物車商品同步處理
購物車結(jié)算事件發(fā)布
訂閱訂單成功創(chuàng)建事件,進行購物車的清空操作
架構(gòu)模式
如上圖所示,本微服務采用數(shù)據(jù)驅(qū)動的CRUD微服務架構(gòu),并使用Redis數(shù)據(jù)庫進行持久化。 這種類型的服務在單個 ASP.NET Core Web API 項目中即可實現(xiàn)所有功能,該項目包括數(shù)據(jù)模型類、業(yè)務邏輯類及其數(shù)據(jù)訪問類。其項目結(jié)構(gòu)如下:
核心技術(shù)選型:
ASP.NET Core Web API
Entity Framework Core
Redis
Swashbuckle(可選)
Autofac
Eventbus
Newtonsoft.Json
實體建模和持久化
該微服務的核心領域?qū)嶓w是購物車,其類圖如下:
其中 CustomerBasket與 BasketItem為一對多關系,使用倉儲模式進行持久化。
通過對?CustomerBasket對象進行json格式的序列化和反序列化來完成在redis中的持久化和讀取。
以單例模式注入redis連接?ConnectionMultiplexer,該對象最終通過構(gòu)造函數(shù)注入到?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);
});
事件的注冊和消費
在本服務中主要需要處理以下事件的發(fā)布和消費:
事件發(fā)布:當用戶點擊購物車結(jié)算時,發(fā)布用戶結(jié)算事件。
事件消費:訂單創(chuàng)建成功后,進行購物車的清空
事件消費:商品價格更新后,進行購物車相關商品的價格同步
private void ConfigureEventBus(IApplicationBuilder app)
{
? ?var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
? ?eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
? ?eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
}
以上都是基于事件總線來達成。
認證和授權(quán)
購物車管理界面是需要認證和授權(quán)。那自然需要與上游的 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獲取該中間件的啟用狀態(tài),通過請求參數(shù)指定:即通過 http://localhost:5103/failing?enable和 http://localhost:5103/failing?disable來手動中斷和恢復服務,來模擬斷路,以便用于測試斷路器模式。 開啟斷路后,當訪問購物車頁面時,Polly在重試指定次數(shù)依然無法訪問服務時,就會拋出 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過濾器來實現(xiàn)的。
2. 模型驗證過濾器是通過繼承?ActionFilterAttribute特性實現(xiàn)的?ValidateModelStateFilter來獲取模型狀態(tài)中的錯誤。
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認證授權(quán)集成
因為默認啟用了安全認證,所以為了方便在SwaggerUI界面進行測試,那么我們就必須為其集成認證授權(quán)。代碼如下:
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. 配置授權(quán)Url
2. 配置TokenUrl
3. 指定授權(quán)范圍
4. 注入授權(quán)檢查過濾器?AuthorizeCheckOperationFilter用于攔截需要授權(quán)的請求
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存儲。
總結(jié)
以上是生活随笔為你收集整理的eShopOnContainers 知多少[7]:Basket microservice的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你准备好了在云中工作吗?
- 下一篇: 微软热门开源项目及代码库地址