标签:tar line view 扩展 动作 group 创建 lstat 体验
接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务。通过本篇阅读,您便可以开始学会在 Claptrap 框架中使用 Minion 进行异步的业务处理。
Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架。如果您是首次阅读本系列文章。建议可以先从本文末尾的入门文章开始了解。
本篇,我通过实现 “商品下单” 的需求来了解一下如何在已有的项目样例中使用 Minion 来完成异步的业务处理。
首先,先了解一下本篇需要涉及的业务用例:
本篇虽然重点在于 Minion 的使用,不过由于需要使用到一个新的 OrderGrain 对象,因此还是需要使用到前一篇 “定义 Claptrap” 的相关知识。
Minion 是一种特殊的 Claptrap,它与其 MasterClaptrap 之间的关系如下图所示:
其主体开发流程和 Claptrap 类似,只是有所删减。对比如下:
步骤 | Claptrap | Minion |
---|---|---|
定义 ClaptrapTypeCode | ? | ? |
定义 State | ? | ? |
定义 Grain 接口 | ? | ? |
实现 Grain | ? | ? |
注册 Grain | ? | ? |
定义 EventCode | ? | |
定义 Event | ? | |
实现 EventHandler | ? | ? |
注册 EventHandler | ? | ? |
实现 IInitialStateDataFactory | ? | ? |
这个删减的原因是由于 Minion 是 Claptrap 的事件消费者,所以事件相关的定义不需要处理。但是其他的部分仍然是必须的。
本篇开始,我们将不再罗列相关代码所在的具体文件位置,希望读者能够自行在项目中进行查找,以便熟练的掌握。
基于前一篇 “定义 Claptrap” 相关的知识,我们此处实现一个 OrderGrain 用来表示订单下单操作。为节约篇幅,我们只罗列其中关键的部分。
订单状态的定义如下:
using System.Collections.Generic; using Newbe.Claptrap; namespace HelloClaptrap.Models.Order { public class OrderState : IStateData { public bool OrderCreated { get; set; } public string UserId { get; set; } public Dictionary<string, int> Skus { get; set; } } }
订单创建事件的定义如下:
using System.Collections.Generic; using Newbe.Claptrap; namespace HelloClaptrap.Models.Order.Events { public class OrderCreatedEvent : IEventData { public string UserId { get; set; } public Dictionary<string, int> Skus { get; set; } } }
using System.Threading.Tasks; using HelloClaptrap.Actors.Order.Events; using HelloClaptrap.IActor; using HelloClaptrap.Models; using HelloClaptrap.Models.Order; using HelloClaptrap.Models.Order.Events; using Newbe.Claptrap; using Newbe.Claptrap.Orleans; using Orleans; namespace HelloClaptrap.Actors.Order { [ClaptrapEventHandler(typeof(OrderCreatedEventHandler), ClaptrapCodes.OrderCreated)] public class OrderGrain : ClaptrapBoxGrain<OrderState>, IOrderGrain { private readonly IGrainFactory _grainFactory; public OrderGrain(IClaptrapGrainCommonService claptrapGrainCommonService, IGrainFactory grainFactory) : base(claptrapGrainCommonService) { _grainFactory = grainFactory; } public async Task CreateOrderAsync(CreateOrderInput input) { var orderId = Claptrap.State.Identity.Id; // throw exception if order already created if (StateData.OrderCreated) { throw new BizException($"order with order id already created : {orderId}"); } // get items from cart var cartGrain = _grainFactory.GetGrain<ICartGrain>(input.CartId); var items = await cartGrain.GetItemsAsync(); // update inventory for each sku foreach (var (skuId, count) in items) { var skuGrain = _grainFactory.GetGrain<ISkuGrain>(skuId); await skuGrain.UpdateInventoryAsync(-count); } // remove all items from cart await cartGrain.RemoveAllItemsAsync(); // create a order var evt = this.CreateEvent(new OrderCreatedEvent { UserId = input.UserId, Skus = items }); await Claptrap.HandleEventAsync(evt); } } }
从系列开头到此,我们从未提及数据库相关的操作。因为当您在使用 Claptrap 框架时,绝大多数的操作都已经被 “事件的写入” 和 “状态的更新” 代替了,故而完全不需要亲自编写数据库操作。
不过,由于 Claptrap 通常是对应单体对象(一个订单,一个 SKU,一个购物车)而设计的,因而无法获取全体(所有订单,所有 SKU,所有购物车)的数据情况。此时,就需要将状态数据持久化到另外的持久化结构中(数据库,文件,缓存等)以便完成全体情况的查询或其他操作。
在 Claptrap 框架中引入了 Minion 的概念来解决上述的需求。
接下来,我们就在样例中引入一个 OrderDbGrain (一个 Minion)来异步完成 OrderGrain 的订单入库操作。
namespace HelloClaptrap.Models { public static class ClaptrapCodes { #region Cart public const string CartGrain = "cart_claptrap_newbe"; private const string CartEventSuffix = "_e_" + CartGrain; public const string AddItemToCart = "addItem" + CartEventSuffix; public const string RemoveItemFromCart = "removeItem" + CartEventSuffix; public const string RemoveAllItemsFromCart = "remoeAllItems" + CartEventSuffix; #endregion #region Sku public const string SkuGrain = "sku_claptrap_newbe"; private const string SkuEventSuffix = "_e_" + SkuGrain; public const string SkuInventoryUpdate = "inventoryUpdate" + SkuEventSuffix; #endregion #region Order public const string OrderGrain = "order_claptrap_newbe"; private const string OrderEventSuffix = "_e_" + OrderGrain; public const string OrderCreated = "orderCreated" + OrderEventSuffix; + public const string OrderDbGrain = "db_order_claptrap_newbe"; #endregion } }
Minion 是一种特殊的 Claptrap,换言之,它也是一种 Claptrap。而 ClaptrapTypeCode 对于 Claptrap 来说是必需的,因而需要增加此定义。
由于本样例只需要向数据库写入一条订单记录就可以了,并不需要在 State 中任何数据,因此该步骤在本样例中其实并不需要。
+ using HelloClaptrap.Models; + using Newbe.Claptrap; + using Newbe.Claptrap.Orleans; + + namespace HelloClaptrap.IActor + { + [ClaptrapMinion(ClaptrapCodes.OrderGrain)] + [ClaptrapState(typeof(NoneStateData), ClaptrapCodes.OrderDbGrain)] + public interface IOrderDbGrain : IClaptrapMinionGrain + { + } + }
星际宗师:因为星际争霸比赛节奏快,信息量大,选手很容易忽视或误判部分信息,因此经常发生 “选手看不到发生在眼皮底下的关键事件” 的搞笑失误。玩家们由此调侃星际玩家都是瞎子(曾经真的有一场盲人和职业选手的对决),段位越高,瞎得越严重,职业星际选手清一色的盲人。
+ using System.Collections.Generic; + using System.Threading.Tasks; + using HelloClaptrap.Actors.DbGrains.Order.Events; + using HelloClaptrap.IActor; + using HelloClaptrap.Models; + using Newbe.Claptrap; + using Newbe.Claptrap.Orleans; + + namespace HelloClaptrap.Actors.DbGrains.Order + { + [ClaptrapEventHandler(typeof(OrderCreatedEventHandler), ClaptrapCodes.OrderCreated)] + public class OrderDbGrain : ClaptrapBoxGrain<NoneStateData>, IOrderDbGrain + { + public OrderDbGrain(IClaptrapGrainCommonService claptrapGrainCommonService) + : base(claptrapGrainCommonService) + { + } + + public async Task MasterEventReceivedAsync(IEnumerable<IEvent> events) + { + foreach (var @event in events) + { + await Claptrap.HandleEventAsync(@event); + } + } + + public Task WakeAsync() + { + return Task.CompletedTask; + } + } + }
此处,由于我们将 OrderDbGrain 定义在单独的程序集,因此,需要额外的注册这个程序集。如下所示:
using System; using Autofac; using HelloClaptrap.Actors.Cart; using HelloClaptrap.Actors.DbGrains.Order; using HelloClaptrap.IActor; using HelloClaptrap.Repository; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newbe.Claptrap; using Newbe.Claptrap.Bootstrapper; using NLog.Web; using Orleans; namespace HelloClaptrap.BackendServer { public class Program { public static void Main(string[] args) { var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger(); try { logger.Debug("init main"); CreateHostBuilder(args).Build().Run(); } catch (Exception exception) { //NLog: catch setup errors logger.Error(exception, "Stopped program because of exception"); throw; } finally { // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) NLog.LogManager.Shutdown(); } } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .UseClaptrap( builder => { builder .ScanClaptrapDesigns(new[] { typeof(ICartGrain).Assembly, typeof(CartGrain).Assembly, + typeof(OrderDbGrain).Assembly }) .ConfigureClaptrapDesign(x => x.ClaptrapOptions.EventCenterOptions.EventCenterType = EventCenterType.OrleansClient); }, builder => { builder.RegisterModule<RepositoryModule>(); }) .UseOrleansClaptrap() .UseOrleans(builder => builder.UseDashboard(options => options.Port = 9000)) .ConfigureLogging(logging => { logging.ClearProviders(); logging.SetMinimumLevel(LogLevel.Trace); }) .UseNLog(); } }
+ using System.Threading.Tasks; + using HelloClaptrap.Models.Order.Events; + using HelloClaptrap.Repository; + using Newbe.Claptrap; + using Newtonsoft.Json; + + namespace HelloClaptrap.Actors.DbGrains.Order.Events + { + public class OrderCreatedEventHandler + : NormalEventHandler<NoneStateData, OrderCreatedEvent> + { + private readonly IOrderRepository _orderRepository; + + public OrderCreatedEventHandler( + IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + public override async ValueTask HandleEvent(NoneStateData stateData, + OrderCreatedEvent eventData, + IEventContext eventContext) + { + var orderId = eventContext.State.Identity.Id; + await _orderRepository.SaveAsync(eventData.UserId, orderId, JsonConvert.SerializeObject(eventData.Skus)); + } + } + }
实际上为了节约篇幅,我们已经在 “实现 Grain” 章节的代码中进行注册。
由于 StateData 没有特殊定义,因此也不需要实现 IInitialStateDataFactory。
样例中,我们增加了 OrderController 用来下单和查询订单。读者可以在源码进行查看。
读者可以使用一下步骤进行实际的效果测试:
/api/cart/123
{“skuId”:”yueluo-666”,”count”:30} 向 123 号购物车加入 30 单位的 yueluo-666 号浓缩精华。/api/order
{“userId”:”999”,”cartId”:”123”} 以 999 userId 的身份,从 123 号购物车进行下单。/api/order
下单成功后可以,通过该 API 查看到下单完成的订单。/api/sku/yueluo-666
可以通过 SKU API 查看下单后的库存余量。至此,我们就完成了 “商品下单” 这个需求的基础内容。通过该样例可以初步了解多个 Claptrap 可以如何合作,以及如何使用 Minion 完成异步任务。
不过,还有一些问题,我们将在后续展开讨论。
您可以从以下地址来获取本文章对应的源代码:
最近作者正在构建以反应式
、Actor模式
和事件溯源
为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap
本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。
联系方式:
您还可以查阅本系列的其他选文:
理论入门篇
术语介绍篇
实现入门篇
样例实践篇
其他番外篇
GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap
Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap
您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 claptrap.newbe.pro。
轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单
标签:tar line view 扩展 动作 group 创建 lstat 体验
原文地址:https://www.cnblogs.com/newbe36524/p/13569458.html