轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单
接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务。通过本篇阅读,您便可以开始学会在 Claptrap 框架中使用 Minion 进行异步的业务处理。
Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架。如果您是首次阅读本系列文章。建议可以先从本文末尾的入门文章开始了解。
开篇摘要
本篇,我通过实现 “商品下单” 的需求来了解一下如何在已有的项目样例中使用 Minion 来完成异步的业务处理。
首先,先了解一下本篇需要涉及的业务用例:
- 用户可以进行下单操作,下单时将使用当前购物车中的所有 SKU 形成一个订单。
- 下单后将会扣除相关 SKU 的库存。如果某一 SKU 库存不足,则下单失败。
- 下单操作仅到扣减库存成功为止,后续步骤不需要本样例讨论范围。因此,本样例在成功下单之后会在数据库中生成一条订单记录,表示订单创建结束。
本篇虽然重点在于 Minion 的使用,不过由于需要使用到一个新的 OrderGrain 对象,因此还是需要使用到前一篇 “定义 Claptrap” 的相关知识。
Minion 是一种特殊的 Claptrap,它与其 MasterClaptrap 之间的关系如下图所示:
其主体开发流程和 Claptrap 类似,只是有所删减。对比如下:
步骤 | Claptrap | Minion |
---|---|---|
定义 ClaptrapTypeCode | ||
定义 State | ||
定义 Grain 接口 | ||
实现 Grain | ||
注册 Grain | ||
定义 EventCode | ||
定义 Event | ||
实现 EventHandler | ||
注册 EventHandler | ||
实现 IInitialStateDataFactory |
这个删减的原因是由于 Minion 是 Claptrap 的事件消费者,所以事件相关的定义不需要处理。但是其他的部分仍然是必须的。
本篇开始,我们将不再罗列相关代码所在的具体文件位置,希望读者能够自行在项目中进行查找,以便熟练的掌握。
实现 OrderGrain
基于前一篇 “定义 Claptrap” 相关的知识,我们此处实现一个 OrderGrain 用来表示订单下单操作。为节约篇幅,我们只罗列其中关键的部分。
OrderState
订单状态的定义如下:
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; }
}
}
- OrderCreated 表示订单是否已经创建,避免重复创建订单
- UserId 下单用户 Id
- Skus 订单包含的 SkuId 和订单量
OrderCreatedEvent
订单创建事件的定义如下:
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; }
}
}
OrderGrain
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);
}
}
}
- OrderGrain 实现订单的创建核心逻辑,其中的 CreateOrderAsync 方法完成购物车数据获取,库存扣减相关的动作。
- OrderCreatedEvent 执行成功后将会更新 State 中相关的字段,此处就不在列出了。
通过 Minion 向数据库保存订单数据
从系列开头到此,我们从未提及数据库相关的操作。因为当您在使用 Claptrap 框架时,绝大多数的操作都已经被 “事件的写入” 和 “状态的更新” 代替了,故而完全不需要亲自编写数据库操作。
不过,由于 Claptrap 通常是对应单体对象(一个订单,一个 SKU,一个购物车)而设计的,因而无法获取全体(所有订单,所有 SKU,所有购物车)的数据情况。此时,就需要将状态数据持久化到另外的持久化结构中(数据库,文件,缓存等)以便完成全体情况的查询或其他操作。
在 Claptrap 框架中引入了 Minion 的概念来解决上述的需求。
接下来,我们就在样例中引入一个 OrderDbGrain (一个 Minion)来异步完成 OrderGrain 的订单入库操作。
定义 ClaptrapTypeCode
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
由于本样例只需要向数据库写入一条订单记录就可以了,并不需要在 State 中任何数据,因此该步骤在本样例中其实并不需要。
定义 Grain 接口
+ 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
+ {
+ }
+ }
- ClaptrapMinion 用来标记该 Grain 是一个 Minion,其中的 Code 指向其对应的 MasterClaptrap。
- ClaptrapState 用来标记 Claptrap 的 State 数据类型。前一步,我们阐明该 Minion 并不需要 StateData,因此使用 NoneStateData 这一框架内置类型来代替。
- IClaptrapMinionGrain 是区别于 IClaptrapGrain 的 Minion 接口。如果一个 Grain 是 Minion ,则需要继承该接口。
- ClaptrapCodes.OrderGrain 和 ClaptrapCodes.OrderDbGrain 是两个不同的字符串,希望读者不是星际宗师。
星际宗师:因为星际争霸比赛节奏快,信息量大,选手很容易忽视或误判部分信息,因此经常发生 “选手看不到发生在眼皮底下的关键事件” 的搞笑失误。玩家们由此调侃星际玩家都是瞎子(曾经真的有一场盲人和职业选手的对决),段位越高,瞎得越严重,职业星际选手清一色的盲人。
实现 Grain
+ 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;
+ }
+ }
+ }
- MasterEventReceivedAsync 是定义自 IClaptrapMinionGrain 的方法,表示实时接收来自 MasterClaptrap 的事件通知。此处暂不展开说明,按照上文模板实现即可。
- WakeAsync 是定义自 IClaptrapMinionGrain 的方法,表示 MasterClaptrap 主动唤醒 Minion 的操作。此处暂不展开说明,按照上文模板实现即可。
- 当读者查看源码时,会发现该类被单独定义在一个程序集当中。这只是一种分类办法,可以理解为将 Minion 和 MasterClaptrap 分别放置在两个不同的项目中进行分类。实际上放在一起也没有问题。
注册 Grain
此处,由于我们将 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 = ))
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog();
}
}
实现 EventHandler
+ 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));
+ }
+ }
+ }
- IOrderRepository 是直接操作存储层的接口,用于订单的增删改查。此处调用该接口实现订单数据库的入库操作。
注册 EventHandler
实际上为了节约篇幅,我们已经在 “实现 Grain” 章节的代码中进行注册。
实现 IInitialStateDataFactory
由于 StateData 没有特殊定义,因此也不需要实现 IInitialStateDataFactory。
修改 Controller
样例中,我们增加了 OrderController 用来下单和查询订单。读者可以在源码进行查看。
读者可以使用一下步骤进行实际的效果测试:
- POST
/api/cart/123
{“skuId”:”yueluo-666”,”count”:30} 向 123 号购物车加入 30 单位的 yueluo-666 号浓缩精华。 - POST
/api/order
{“userId”:”999”,”cartId”:”123”} 以 999 userId 的身份,从 123 号购物车进行下单。 - GET
/api/order
下单成功后可以,通过该 API 查看到下单完成的订单。 - GET
/api/sku/yueluo-666
可以通过 SKU API 查看下单后的库存余量。
小结
至此,我们就完成了 “商品下单” 这个需求的基础内容。通过该样例可以初步了解多个 Claptrap 可以如何合作,以及如何使用 Minion 完成异步任务。
不过,还有一些问题,我们将在后续展开讨论。
您可以从以下地址来获取本文章对应的源代码:
最后但是最重要!
最近作者正在构建以反应式
、Actor模式
和事件溯源
为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap
本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。
联系方式:
- Github Issue
- Gitee Issue
- 公开邮箱 newbe-claptrap@googlegroups.com (发送到该邮箱的内容将被公开)
- Gitter
- QQ 群 610394020
您还可以查阅本系列的其他选文:
理论入门篇
术语介绍篇
- 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,管理商品库存
样例实践篇
其他番外篇
- 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
- 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
- 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
- docker-mcr 助您全速下载 dotnet 镜像
- 十多位全球技术专家,为你献上近十个小时的.Net 微服务介绍
- 年轻的樵夫哟,你掉的是这个免费 8 核 4G 公网服务器,还是这个随时可用的 Docker 实验平台?
GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap
Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap
您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 claptrap.newbe.pro。
- 本文作者: newbe36524
- 本文链接: https://www.newbe.pro/Newbe.Claptrap/Get-Started-4/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单的更多相关文章
- Newbe.Claptrap 框架入门,第一步 —— 开发环境准备
Newbe.Claptrap 框架依托于一些关键性的基础组件和一些可选的辅助组件.本篇我们来介绍一下如何准备一个开发环境. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如 ...
- Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
让我们来实现一个简单的 “电商购物车” 需求来了解一下如何使用 Newbe.Claptrap 进行开发. 业务需求 实现一个简单的 “电商购物车” 需求,这里实现几个简单的业务: 获取当前购物车中的商 ...
- Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存
接上一篇 Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开始学会添加一个全 ...
- Newbe.Claptrap 框架入门,第二步 —— 创建项目
接上一篇 Newbe.Claptrap 框架入门,第一步 -- 开发环境准备 ,我们继续了解如何创建一个 Newbe.Claptrap 项目. Newbe.Claptrap 是一个用于轻松应对并发问题 ...
- Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
接上一篇 Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开始尝试使用 ...
- 轻松应对并发问题,Newbe.Claptrap 框架中 State 和 Event 应该如何理解?
Newbe.Claptrap 框架中 State 和 Event 应该如何理解?最近整理了一下项目的术语表.今天就谈谈什么是 Event 和 State. Newbe.Claptrap 是一个用于轻松 ...
- 轻松应对并发问题,简易的火车票售票系统,Newbe.Claptrap 框架用例,第一步 —— 业务分析
Newbe.Claptrap 框架非常适合于解决具有并发问题的业务系统.火车票售票系统,就是一个非常典型的场景用例. 本系列我们将逐步从业务.代码.测试和部署多方面来介绍,如何使用 Newbe.Cla ...
- Newbe.Claptrap 框架如何实现多级生命周期控制?
Newbe.Claptrap 框架如何实现多级生命周期控制?最近整理了一下项目的术语表.今天就谈谈什么是 Claptrap Lifetime Scope. 特别感谢 kotone 为本文提供的校对建议 ...
- Newbe.Claptrap 框架如何实现 Claptrap 的多样性?
Newbe.Claptrap 框架如何实现 Claptrap 的多样性?最近整理了一下项目的术语表.今天就谈谈什么是 Claptrap Design 和 Claptrap Factory. 特别感谢 ...
随机推荐
- Django学习路
1.脚本不能随便运行,没准 linux 运行完就上不去了 2.pip 在 linux 上 写 pip3 同理 python 写为 python3 3.在 pycharm 上安装库之后,在命令提示符中依 ...
- PHP array_intersect_uassoc() 函数
实例 比较两个数组的键名和键值(使用用户自定义函数比较键名),并返回交集: <?phpfunction myfunction($a,$b){if ($a===$b){return 0;}retu ...
- PHP timezone_offset_get() 函数
------------恢复内容开始------------ 实例 返回相对于 GMT 的时区偏移: <?php$tz=timezone_open("Asia/Taipei" ...
- PDOStatement::execute
PDOStatement::execute — 执行一条预处理语句(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 bool PDOStatement:: ...
- 2019 HL SC day1
今天讲的是图论大体上分为:有向图的强连通分量,有向图的完全图:竞赛图,无向图的的割点,割边,点双联通分量,变双联通分量以及圆方树 2-sat问题 支配树等等. 大体上都知道是些什么东西 但是仍需要写一 ...
- luogu P4095 [HEOI2013]Eden 的新背包问题 多重背包 背包的合并
LINK:Eden 的新背包问题 就是一个多重背包 每次去掉一个物品 询问钱数为w所能买到的最大值. 可以对于每次Q暴力dp 利用单调队列优化多重背包 这样复杂度是Qnm的. 发现过不了n==10的点 ...
- windows:进程查杀
windows平台中,某些进程做了各种保护,比如hook了terminateProcess,又或者注册了进程终止函数的回调.当调用这些API或任务管理器终止该进程时,会被绕过,典型如某些杀毒软件,怎么 ...
- TCP为什么做三次握手、四次挥手
TCP 为什么做三次握手.四次挥手? TCP 是为了解决可靠传输出现的.为了实现可靠性,TCP 做了流量控制.拥塞控制,并且在建立.关闭连接前做些机制:三次握手.四次挥手. 三次握手是为了让客户端.服 ...
- vmware15虚拟机安装教程
自己碰到的问题:本人win7 64位旗舰版系统.之前用VMware12pro版本的软件,在安装Ubuntu18.04之后,有时候开启Ubuntu虚拟机时有点问题,重启就可以了.但是不稳定,所以改用VM ...
- Eclipse Java EE IDE for Web Developers 4.5.1 安装hibername tools 插件
方式一:在线安装(太慢) 方式二:离线安装,下载hibernate tools 插件到本地,然后在eclipse菜单栏点击 help: ①添加插件,选择下载后的插件,内容框中可选择hibernate ...