Newbe.Claptrap|Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存
接上一篇 Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务。通过本篇阅读,您便可以开始学会添加一个全新的 Claptrap。
Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架。如果您是首次阅读本系列文章。建议可以先从本文末尾的入门文章开始了解。开篇摘要 本篇,我通过实现 “管理库存” 的需求来了解一下如何在已有的项目样例中定义一个 Claptrap。
结合前一篇的基本步骤,定义 Claptrap 只要而外增加一些步骤就可以了。完整的步骤如下所示,其中标记为 “新内容” 的部分属于本篇的区别于前篇的新内容:
- 定义 ClaptrapTypeCode (新内容)
- 定义 State (新内容)
- 定义 Grain 接口 (新内容)
- 实现 Grain (新内容)
- 注册 Grain (新内容)
- 定义 EventCode
- 定义 Event
- 实现 EventHandler
- 注册 EventHandler
- 实现 IInitialStateDataFactory (新内容)
- 修改 Controller
本篇实现的业务用例:
- 实现表示库存数据的 SKU(Stock keeping Unit) 对象。
- 能够对 SKU 进行更新和读取。
打开
HelloClaptrap.Models
项目中的 ClaptrapCodes
类。添加 SKU 的 ClaptrapTypeCode。
namespace HelloClaptrap.Models { public static class ClaptrapCodes { 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; #region Sku+public const string SkuGrain = "sku_claptrap_newbe"; +private const string SkuEventSuffix = "_e_" + SkuGrain; #endregion } }
定义 State State 在 Actor 模式中代表了 Actor 对象当前的数据表现。
由于 Claptrap 是基于事件溯源模式的 Actor。因此定义恰好的 State 非常重要。
在该示例当中,我们只需要记录当前 SKU 的库存即可,因此,State 的设计非常的简单。
在
HelloClaptrap.Models
项目添加 Sku
文件夹,并在该文件夹下创建 SkuState
类。添加如下代码:
+ using Newbe.Claptrap; + + namespace HelloClaptrap.Models.Sku + { +public class SkuState : IStateData +{ +public int Inventory { get; set; } +} + }
Inventory 表示当前 SKU 的库存。
IStateData
接口是框架中表示 State 的空接口,用于在泛型推断时使用。定义 Grain 接口 定义 Grain 接口的定义,才能够提供外部与 Claptrap 的互操作性。
在
HelloClaptrap.IActors
项目中添加 ISkuGrain
接口。添加接口以及 Attribute。
+ using System.Threading.Tasks; + using HelloClaptrap.Models; + using HelloClaptrap.Models.Sku; + using Newbe.Claptrap; + using Newbe.Claptrap.Orleans; + + namespace HelloClaptrap.IActor + { +[ClaptrapState(typeof(SkuState), ClaptrapCodes.SkuGrain)] +public interface ISkuGrain : IClaptrapGrain +{ +/// +/// Get latest inventory of this sku +/// +///+Task GetInventoryAsync(); + +/// +/// Update inventory by add diff, diff could be negative number +/// +/// +/// Inventory after updating +TaskUpdateInventoryAsync(int diff); +} + }
其中增加了以下内容:
- 标记了
ClaptrapState
,使得 State 与 Grain 进行关联。 - 接口继承了
IClaptrapGrain
,这是框架定义的 Grain 接口,这是依托于 Orleans 运行必须继承的接口。 - 增加了 GetInventoryAsync 方法,表示 “获取当前库存”。
- 增加了 UpdateInventoryAsync 方法,表示 “增量更新当前库存”。
diff > 0
表示增加库存,diff < 0
表示减少库存。 - 需要注意的是 Grain 的方法定义有一定限制。详细可以参见《Developing a Grain》。
在
HelloClaptrap.Actors
项目新建 Sku
文件夹,并在该文件夹中添加 SkuGrain
类。+ using System; + using System.Threading.Tasks; + using HelloClaptrap.IActor; + using HelloClaptrap.Models; + using HelloClaptrap.Models.Sku; + using Newbe.Claptrap; + using Newbe.Claptrap.Orleans; + + namespace HelloClaptrap.Actors.Sku + { +public class SkuGrain : ClaptrapBoxGrain, ISkuGrain +{ +public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService) +: base(claptrapGrainCommonService) +{ +} + +public TaskGetInventoryAsync() +{ +return Task.FromResult(StateData.Inventory); +} + +public async Task UpdateInventoryAsync(int diff) +{ +if (diff == 0) +{ +throw new BizException("diff can`t be 0"); +} + +var old = StateData.Inventory; +var newInventory = old + diff; +if (newInventory < 0) +{ +throw new BizException( +$"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}"); +} + +throw new NotImplementedException(); +} +} + }
其中增加了以下内容:
- 继承
ClaptrapBoxGrain
并实现ISkuGrain
,ClaptrapBoxGrain
是框架定义的 Grain 基类,其中的泛型参数表示对应的 State 类型。 - 实现 GetInventoryAsync 方法,从 StateData 中读取当前的库存。
- 实现 UpdateInventoryAsync 方法,添加业务判断代码,若不满足业务操作的条件则抛出异常。
- UpdateInventoryAsync 的最后我们现在抛出 NotImplementedException ,因为当前事件还没有定义,需要等待后续的代码实现。
- BizException 是一个自定义异常,可以自行添加。实际开发中也可以不使用抛出异常的方式表示业务中断,改用状态码或者其他返回值也是可以的。
由于示例代码采用的是程序集范围内扫描,因此实际上不需要进行修改。
这里指出发生注册的位置:
打开
HelloClaptrap.BackendServer
项目的 Program
类。using System; using Autofac; using HelloClaptrap.Actors.Cart; 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 IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) .UseClaptrap( builder => { +builder +.ScanClaptrapDesigns(new[] +{ +typeof(ICartGrain).Assembly, +typeof(CartGrain).Assembly, +}); }, builder => { builder.RegisterModule(); }) .UseOrleansClaptrap() .UseOrleans(builder => builder.UseDashboard(options => options.Port = 9000)) .ConfigureLogging(logging => { logging.ClearProviders(); logging.SetMinimumLevel(LogLevel.Trace); }) .UseNLog(); } }
因为 ISkuGrain 和 SkuGrain 分别于 ICartGrain 和 CartGrain 属于同一程序集,因而此处不需要修改。
定义 EventCode 前面我们已经实现了 Claptrap 的主要部分,但唯独没有完成更新库存的操作。这是因为更新库存是需要对 State 进行更新的。而我们都知道 Claptrap 是基于事件溯源的 Actor 模式,对 State 的更新需要通过事件才能完成。故而由这里开始,我们来通过事件更新库存。
EventCode 是 Claptrap 系统每个事件的唯一编码。其在事件的识别,序列化等方面起到了重要的作用。
打开
HelloClaptrap.Models
项目中的 ClaptrapCodes
类。添加 “更新库存” 的 EventCode。
namespace HelloClaptrap.Models { public static class ClaptrapCodes { #region Cartpublic 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 Skupublic const string SkuGrain = "sku_claptrap_newbe"; private const string SkuEventSuffix = "_e_" + SkuGrain; +public const string SkuInventoryUpdate = "inventoryUpdate" + SkuEventSuffix; #endregion } }
定义 Event Event 是事件溯源的关键。用于改变 Claptrap 中的 State。并且 Event 会被持久化在持久层。
在
HelloClaptrap.Models
项目的 Sku/Events
文件夹下创建 InventoryUpdateEvent
类。添加如下代码:
+ using Newbe.Claptrap; + + namespace HelloClaptrap.Models.Sku.Events + { +public class InventoryUpdateEvent : IEventData +{ +public int Diff { get; set; } +public int NewInventory { get; set; } +} + }
- Diff 表示此次更新库存的数额,
diff > 0
表示增加库存,diff < 0
表示减少库存。 - NewInventory 表示更新之后的库存。此处,提前给出一个建议,但由于篇幅问题,不展开讨论:建议在事件中包含 State 的更新后数据。
EventHandler
用于将事件更新到 Claptrap 的 State
上。在
HelloClaptrap.Actors
项目的 Sku/Events
文件夹下创建 InventoryUpdateEventHandler
类。添加如下代码:
+ using System.Threading.Tasks; + using HelloClaptrap.Models.Sku; + using HelloClaptrap.Models.Sku.Events; + using Newbe.Claptrap; + + namespace HelloClaptrap.Actors.Sku.Events + { +public class InventoryUpdateEventHandler +: NormalEventHandler +{ +public override ValueTask HandleEvent(SkuState stateData, +InventoryUpdateEvent eventData, +IEventContext eventContext) +{ +stateData.Inventory = eventData.NewInventory; +return new ValueTask(); +} +} + }
- 因为事件中已经包含了更新后的库存,故而直接对 StateData 进行赋值即可。
打开
HelloClaptrap.Actors
项目的 SkuGrain
类。使用 Attribute 进行标记,并修改 UpdateInventoryAsync 执行事件。
using System.Threading.Tasks; + using HelloClaptrap.Actors.Sku.Events; using HelloClaptrap.IActor; using HelloClaptrap.Models; using HelloClaptrap.Models.Sku; + using HelloClaptrap.Models.Sku.Events; using Newbe.Claptrap; using Newbe.Claptrap.Orleans; namespace HelloClaptrap.Actors.Sku { +[ClaptrapEventHandler(typeof(InventoryUpdateEventHandler), ClaptrapCodes.SkuInventoryUpdate)] public class SkuGrain : ClaptrapBoxGrain, ISkuGrain { public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService) : base(claptrapGrainCommonService) { }public TaskGetInventoryAsync() { return Task.FromResult(StateData.Inventory); }public async Task UpdateInventoryAsync(int diff) { if (diff == 0) { throw new BizException("diff can`t be 0"); }var old = StateData.Inventory; var newInventory = old + diff; if (newInventory < 0) { throw new BizException( $"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}"); }-throw new NotImplementedException(); +var evt = this.CreateEvent(new InventoryUpdateEvent +{ +Diff = diff, +NewInventory = newInventory +}); +await Claptrap.HandleEventAsync(evt); +return StateData.Inventory; } } }
实现 IInitialStateDataFactory 前面我们已经完成了库存的查询和更新。不过通常来说库存有一个初始数额,我们本节在补充这部分逻辑。
在
HelloClaptrap.Actors
项目的 Sku
文件夹下创建 SkuStateInitHandler
类。+ using System.Threading.Tasks; + using HelloClaptrap.Models.Sku; + using HelloClaptrap.Repository; + using Newbe.Claptrap; + + namespace HelloClaptrap.Actors.Sku + { +public class SkuStateInitHandler : IInitialStateDataFactory +{ +private readonly ISkuRepository _skuRepository; + +public SkuStateInitHandler( +ISkuRepository skuRepository) +{ +_skuRepository = skuRepository; +} + +public async TaskCreate(IClaptrapIdentity identity) +{ +var skuId = identity.Id; +var inventory = await _skuRepository.GetInitInventoryAsync(skuId); +var re = new SkuState +{ +Inventory = inventory +}; +return re; +} +} + }
IInitialStateDataFactory
会在 Claptrap 初次激活时被调用,用来创建 State 的初始值。- 注入
ISkuRepository
从数据库中读取 Sku 对应的库存初始数额,具体的代码此处不进行罗列,读者可以查看样例仓库中的实现。
打开
HelloClaptrap.Actors
项目的 SkuGrain
类。using System.Threading.Tasks; using HelloClaptrap.Actors.Sku.Events; using HelloClaptrap.IActor; using HelloClaptrap.Models; using HelloClaptrap.Models.Sku; using HelloClaptrap.Models.Sku.Events; using Newbe.Claptrap; using Newbe.Claptrap.Orleans; namespace HelloClaptrap.Actors.Sku { +[ClaptrapStateInitialFactoryHandler(typeof(SkuStateInitHandler))] [ClaptrapEventHandler(typeof(InventoryUpdateEventHandler), ClaptrapCodes.SkuInventoryUpdate)] public class SkuGrain : ClaptrapBoxGrain, ISkuGrain { public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService) : base(claptrapGrainCommonService) { }public TaskGetInventoryAsync() { return Task.FromResult(StateData.Inventory); }public async Task UpdateInventoryAsync(int diff) { if (diff == 0) { throw new BizException("diff can`t be 0"); }var old = StateData.Inventory; var newInventory = old + diff; if (newInventory < 0) { throw new BizException( $"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}"); }var evt = this.CreateEvent(new InventoryUpdateEvent { Diff = diff, NewInventory = newInventory }); await Claptrap.HandleEventAsync(evt); return StateData.Inventory; } } }
修改 Controller 前面的所有步骤完成之后,就已经完成了 Claptrap 的所有部分。但由于 Claptrap 无法直接提供与外部程序的互操作性。因此,还需要在在 Controller 层增加一个 API 以便外部进行 “读取库存” 的操作。
在
HelloClaptrap.Web
项目的 Controllers
文件夹下新建 SkuController
类。+ using System.Threading.Tasks; + using HelloClaptrap.IActor; + using Microsoft.AspNetCore.Mvc; + using Orleans; + + namespace HelloClaptrap.Web.Controllers + { +[Route("api/[controller]")] +public class SkuController : Controller +{ +private readonly IGrainFactory _grainFactory; + +public SkuController( +IGrainFactory grainFactory) +{ +_grainFactory = grainFactory; +} + +[HttpGet("{id}")] +public async TaskGetItemsAsync(string id) +{ +var skuGrain = _grainFactory.GetGrain (id); +var inventory = await skuGrain.GetInventoryAsync(); +return Json(new +{ +skuId = id, +inventory = inventory, +}); +} +} + }
- 新增 API 读取特定 SkuId 的库存。按照样例代码的实现,可以传入
yueluo-123
得到库存数额为 666。不存在的 SkuId 将会抛出异常。 - 此处没有创建更新库存的对外 API,因为本示例将在下篇进行下单购物时进行库存操作,此处暂不需要 API。
您可以从以下地址来获取本文章对应的源代码:
- Github
- Gitee
反应式
、Actor模式
和事件溯源
为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。
联系方式:
- Github Issue
- Gitee Issue
- 公开邮箱 newbe-claptrap@googlegroups.com (发送到该邮箱的内容将被公开)
- Gitter
理论入门篇
- Newbe.Claptrap - 一套以 “事件溯源” 和 “Actor 模式” 作为基本理论的服务端开发框架
- Actor 模式
- 事件溯源(Event Sourcing)
- Claptrap
- Minion
- 事件 (Event)
- 状态 (State)
- 状态快照 (State Snapshot)
- Claptrap 设计图 (Claptrap Design)
- Claptrap 工厂 (Claptrap Factory)
- Claptrap Identity
- Claptrap Box
- Claptrap 生命周期(Claptrap Lifetime Scope)
- 序列化(Serialization)
- Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
- Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
- Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存
- 构建一个简易的火车票售票系统,Newbe.Claptrap 框架用例,第一步 —— 业务分析
- 在线体验火车票售票系统
- 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
- 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
- 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
- docker-mcr 助您全速下载 dotnet 镜像
- 十多位全球技术专家,为你献上近十个小时的.Net 微服务介绍
- 年轻的樵夫哟,你掉的是这个免费 8 核 4G 公网服务器,还是这个随时可用的 Docker 实验平台?
Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap
您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 claptrap.newbe.pro。
文章图片
【Newbe.Claptrap|Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存】
- 本文作者: newbe36524
- 本文链接: https://www.newbe.pro/Newbe.Claptrap/Get-Started-3/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
推荐阅读
- android第三方框架(五)ButterKnife
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- typeScript入门基础介绍
- Spring|Spring 框架之 AOP 原理剖析已经出炉!!!预定的童鞋可以识别下发二维码去看了
- 构建App(一)(框架与结构)
- Android|Android sqlite3数据库入门系列
- laravel框架泛解
- Android下的IO库-Okio源码解析(一)|Android下的IO库-Okio源码解析(一) 入门
- 深度学习-入门
- spring事务管理_01:事务管理框架+声明式事务