Newbe.Claptrap框架入门,第三步——定义Claptrap,管理商品库存
接上一篇 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务。通过本篇阅读,您便可以开始学会添加一个全新的 Claptrap。
该开发文档已经过期
该开发文档仅适用于 Newbe.Claptrap 0.7 及以下版本。若要查看最新的开发文档,请移步 https://claptrap.newbe.pro
开篇摘要
本篇,我通过实现“管理库存”的需求来了解一下如何在已有的项目样例中定义一个 Claptrap。
结合前一篇的基本步骤,定义 Claptrap 只要而外增加一些步骤就可以了。完整的步骤如下所示,其中标记为“新内容”的部分属于本篇的区别于前篇的新内容:
- 定义 ClaptrapTypeCode (新内容)
- 定义 State (新内容)
- 定义 Grain 接口 (新内容)
- 实现 Grain (新内容)
- 注册 Grain (新内容)
- 定义 EventCode
- 定义 Event
- 实现 EventHandler
- 注册 EventHandler
- 实现 IInitialStateDataFactory (新内容)
- 修改 Controller
这是一个从下向上的过程,实际的编码过程中开发也可以有所调整。
本篇实现的业务用例:
- 实现表示库存数据的 SKU(Stock keeping Unit) 对象。
- 能够对 SKU 进行更新和读取。
定义 ClaptrapTypeCode
ClaptrapTypeCode 是一个 Claptrap 的唯一编码。其在 State 的识别,序列化等方面起到了重要的作用。
打开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
+ {
+ /// <summary>
+ /// Get latest inventory of this sku
+ /// </summary>
+ /// <returns></returns>
+ Task<int> GetInventoryAsync();
+
+ /// <summary>
+ /// Update inventory by add diff, diff could be negative number
+ /// </summary>
+ /// <param name="diff"></param>
+ /// <returns>Inventory after updating</returns>
+ Task<int> UpdateInventoryAsync(int diff);
+ }
+ }
其中增加了以下内容:
- 标记了
ClaptrapState,使得 State 与 Grain 进行关联。 - 接口继承了
IClaptrapGrain,这是框架定义的 Grain 接口,这是依托于 Orleans 运行必须继承的接口。 - 增加了 GetInventoryAsync 方法,表示“获取当前库存”。
- 增加了 UpdateInventoryAsync 方法,表示“增量更新当前库存”。
diff > 0表示增加库存,diff < 0表示减少库存。 - 需要注意的是 Grain 的方法定义有一定限制。详细可以参见《Developing a Grain》。
实现 Grain
定义好 ISkuGrain 之后,便可以添加代码进行实现。
在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<SkuState>, ISkuGrain
+ {
+ public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService)
+ : base(claptrapGrainCommonService)
+ {
+ }
+
+ public Task<int> GetInventoryAsync()
+ {
+ return Task.FromResult(StateData.Inventory);
+ }
+
+ public async Task<int> 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<SkuState>并实现ISkuGrain,ClaptrapBoxGrain是框架定义的 Grain 基类,其中的泛型参数表示对应的 State 类型。 - 实现 GetInventoryAsync 方法,从 StateData 中读取当前的库存。
- 实现 UpdateInventoryAsync 方法,添加业务判断代码,若不满足业务操作的条件则抛出异常。
- UpdateInventoryAsync 的最后我们现在抛出 NotImplementedException ,因为当前事件还没有定义,需要等待后续的代码实现。
- BizException 是一个自定义异常,可以自行添加。实际开发中也可以不使用抛出异常的方式表示业务中断,改用状态码或者其他返回值也是可以的。
注册 Grain
Claptrap 对应的 Grain 需要在应用程序启动时进行注册,这样框架才能扫描发现。
由于示例代码采用的是程序集范围内扫描,因此实际上不需要进行修改。
这里指出发生注册的位置:
打开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<Startup>(); })
.UseClaptrap(
builder =>
{
+ builder
+ .ScanClaptrapDesigns(new[]
+ {
+ typeof(ICartGrain).Assembly,
+ typeof(CartGrain).Assembly,
+ });
},
builder => { builder.RegisterModule<RepositoryModule>(); })
.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 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
}
}