eShopOnContainers 知多少[7]:Basket microservice

購物車界面

引言

Basket microservice(購物車微服務(wù))主要用于處理購物車的業(yè)務(wù)邏輯嗅骄,包括:

  1. 購物車商品的CRUD
  2. 訂閱商品價格更新事件,進(jìn)行購物車商品同步處理
  3. 購物車結(jié)算事件發(fā)布
  4. 訂閱訂單成功創(chuàng)建事件客情,進(jìn)行購物車的清空操作

架構(gòu)模式

數(shù)據(jù)驅(qū)動/CRUD 微服務(wù)設(shè)計

如上圖所示,本微服務(wù)采用數(shù)據(jù)驅(qū)動的CRUD微服務(wù)架構(gòu)癞己,并使用Redis數(shù)據(jù)庫進(jìn)行持久化膀斋。
這種類型的服務(wù)在單個 ASP.NET Core Web API 項目中即可實現(xiàn)所有功能,該項目包括數(shù)據(jù)模型類痹雅、業(yè)務(wù)邏輯類及其數(shù)據(jù)訪問類仰担。其項目結(jié)構(gòu)如下:


核心技術(shù)選型:

  1. ASP.NET Core Web API
  2. Entity Framework Core
  3. Redis
  4. Swashbuckle(可選)
  5. Autofac
  6. Eventbus
  7. Newtonsoft.Json

實體建模和持久化

該微服務(wù)的核心領(lǐng)域?qū)嶓w是購物車,其類圖如下:

其中CustomerBasketBasketItem為一對多關(guān)系绩社,使用倉儲模式進(jìn)行持久化摔蓝。

  1. 通過對CustomerBasket對象進(jìn)行json格式的序列化和反序列化來完成在redis中的持久化和讀取赂苗。
  2. 以單例模式注入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èi)

在本服務(wù)中主要需要處理以下事件的發(fā)布和消費(fèi):

  1. 事件發(fā)布:當(dāng)用戶點(diǎn)擊購物車結(jié)算時拌滋,發(fā)布用戶結(jié)算事件。
  2. 事件消費(fèi):訂單創(chuàng)建成功后猜谚,進(jìn)行購物車的清空
  3. 事件消費(fèi):商品價格更新后败砂,進(jìn)行購物車相關(guān)商品的價格同步
private void ConfigureEventBus(IApplicationBuilder app)
{
    var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();

    eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
    eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
}

以上都是基于事件總線來達(dá)成。

認(rèn)證和授權(quán)

購物車管理界面是需要認(rèn)證和授權(quán)魏铅。那自然需要與上游的Identity Microservice進(jìn)行銜接吠卷。在啟動類進(jìn)行認(rèn)證中間件的配置。

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();
}

手動啟用斷路器

在該微服務(wù)中吉嫩,定義了一個中斷中間件FailingMiddleware云挟,通過訪問http://localhost:5103/failing獲取該中間件的啟用狀態(tài),通過請求參數(shù)指定:即通過http://localhost:5103/failing?enablehttp://localhost:5103/failing?disable來手動中斷和恢復(fù)服務(wù),來模擬斷路杀迹,以便用于測試斷路器模式送朱。
開啟斷路后魁袜,當(dāng)訪問購物車頁面時敦第,Polly在重試指定次數(shù)依然無法訪問服務(wù)時鞠呈,就會拋出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)";
    }
}
提示購物車服務(wù)暫時不可用

注入過濾器

在配置MVC服務(wù)時指定了兩個過濾器:全局異常過濾器和模型驗證過濾器洼怔。

// 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認(rèn)證授權(quán)集成

因為默認(rèn)啟用了安全認(rèn)證淑蔚,所以為了方便在SwaggerUI界面進(jìn)行測試,那么我們就必須為其集成認(rèn)證授權(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" } }
            });
        }
    }
}

最后

本服務(wù)較之前講的Catalog microservice 而言,主要是多了一個認(rèn)證和redis存儲。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鲸鹦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖莺债,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肺蔚,死亡現(xiàn)場離奇詭異宣羊,居然都是意外死亡璧诵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苛坚,“玉大人娇昙,你說我怎么就攤上這事膳音”〉眨” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵颗胡,是天一觀的道長哑蔫。 經(jīng)常有香客問我,道長弧呐,這世上最難降的妖魔是什么闸迷? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮俘枫,結(jié)果婚禮上腥沽,老公的妹妹穿的比我還像新娘。我一直安慰自己鸠蚪,他們只是感情好今阳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茅信,像睡著了一般盾舌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蘸鲸,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天妖谴,我揣著相機(jī)與錄音,去河邊找鬼酌摇。 笑死膝舅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窑多。 我是一名探鬼主播铸史,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼怯伊!你這毒婦竟也來了琳轿?” 一聲冷哼從身側(cè)響起判沟,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崭篡,沒想到半個月后挪哄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琉闪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年迹炼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颠毙。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡斯入,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛀蜜,到底是詐尸還是另有隱情刻两,我是刑警寧澤滴某,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布磅摹,位于F島的核電站,受9級特大地震影響霎奢,放射性物質(zhì)發(fā)生泄漏帝美。R本人自食惡果不足惜证舟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窗骑。 院中可真熱鬧,春花似錦漆枚、人聲如沸创译。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽软族。三九已至,卻和暖如春残制,著一層夾襖步出監(jiān)牢的瞬間立砸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工初茶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颗祝,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像螺戳,于是被迫代替她去往敵國和親搁宾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 有一個畫面,令我至今難忘倔幼。 一個黃昏盖腿,一位女子開車與一男子開的車相擦,不等女子下車损同,男子氣沖沖地對她一通責(zé)難翩腐。女子...
    帶露珠的翅膀閱讀 255評論 0 4
  • 九月銀川茂卦,風(fēng)總是很大,夜總是很冷蹄梢,在一場開學(xué)季的洗禮中疙筹,踹懷著夢想,漸進(jìn)禁炒。 幾天前的一個下午而咆,小區(qū)門前的幾棵棗樹被...
    淺予南風(fēng)閱讀 1,040評論 0 3
  • 高中時,我有一位閨密幕袱,和她的相遇讓我想起了一首歌――《一個像夏天一個像秋天》暴备。準(zhǔn)確的說,我們初中是校友们豌,那個時候散...
    落日船客閱讀 438評論 1 3