Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
接上一篇 Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务。通过本篇阅读,您便可以开始尝试使用 Claptrap 实现业务了。
开篇摘要
本篇,我通过实现 “清空购物车” 的需求来了解一下如何在已有的项目样例中增加一个业务实现。
主要包含有以下这些步骤:
- 定义 EventCode
- 定义 Event
- 实现 EventHandler
- 注册 EventHandler
- 修改 Grain 接口
- 实现 Grain
- 修改 Controller
这是一个从下向上的过程,实际的编码过程中开发也可以自上而下进行实现。
定义 Event Code
EventCode 是 Claptrap 系统每个事件的唯一编码。其在事件的识别,序列化等方面起到了重要的作用。
打开 HelloClaptrap.Models
项目中的 ClaptrapCodes
类。
添加 “清空购物车事件” 的 EventCode。
- 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;
- + public const string RemoveAllItemsFromCart = "remoeAllItems" + CartEventSuffix;
- }
- }
定义 Event
Event 是事件溯源的关键。用于改变 Claptrap 中的 State。并且 Event 会被持久化在持久层。
在 HelloClaptrap.Models
项目的 Cart/Events
文件夹下创建 RemoveAllItemsFromCartEvent
类。
添加如下代码:
- + using Newbe.Claptrap;
- +
- + namespace HelloClaptrap.Models.Cart.Events
- + {
- + public class RemoveAllItemsFromCartEvent : IEventData
- + {
- + }
- + }
由于在这个简单的业务场景中,清空购物车不需要特定的参数。因此,只要创建空类型即可。
IEventData
接口是框架中表示事件的空接口,用于在泛型推断时使用。
实现 EventHandler
EventHandler
用于将事件更新到 Claptrap 的 State
上。例如此次的业务场景,那么 EventHandler 就负责将 State 购物车中的内容清空即可。
在 HelloClaptrap.Actors
项目的 Cart/Events
文件夹下创建 RemoveAllItemsFromCartEventHandler
类。
添加如下代码:
- + using System.Threading.Tasks;
- + using HelloClaptrap.Models.Cart;
- + using HelloClaptrap.Models.Cart.Events;
- + using Newbe.Claptrap;
- +
- + namespace HelloClaptrap.Actors.Cart.Events
- + {
- + public class RemoveAllItemsFromCartEventHandler
- + : NormalEventHandler<CartState, RemoveAllItemsFromCartEvent>
- + {
- + public override ValueTask HandleEvent(CartState stateData,
- + RemoveAllItemsFromCartEvent eventData,
- + IEventContext eventContext)
- + {
- + stateData.Items = null;
- + return new ValueTask();
- + }
- + }
- + }
这里有一些常见的问题:
NormalEventHandler 是什么?
NormalEventHandler 是框架定义的一个简单基类,用于方便实现 Handler。
其中第一个泛型参数是 Claptrap 对应的 State 类型。结合前篇文档中,我们的购物车 State 类型就是 CartState。
第二个泛型参数是该 Handler 需要处理的 Event 类型。为什么用
stateData.Items = null;
而不用stateData.Items.Clear();
stateData 是保存在内存中的对象,Clear 不会缩小字典已占用的自身内存。当然,一般一个购物车也不会有数十万商品。但其实关键是在于,更新 State 时,需要注意的是 Claptrap 是一种常驻于内存中的对象,数量增加时会加剧内存的消耗。因此,尽可能在 State 中保持更少的数据。
ValueTask 是什么?
可以通过这篇《Understanding the Whys, Whats, and Whens of ValueTask》进行了解。
EventHandler 实现完成之后,不要忘记对其进行单元测试。这里就不罗列了。
注册 EventHandler
实现并测试完 EventHandler 之后,便可以将 EventHandler 进行注册,以便与 EventCode 以及 Claptrap 进行关联。
打开 HelloClaptrap.Actors
项目的 CartGrain
类。
使用 Attribute 进行标记。
- using Newbe.Claptrap;
- using Newbe.Claptrap.Orleans;
- namespace HelloClaptrap.Actors.Cart
- {
- [ClaptrapEventHandler(typeof(AddItemToCartEventHandler), ClaptrapCodes.AddItemToCart)]
- [ClaptrapEventHandler(typeof(RemoveItemFromCartEventHandler), ClaptrapCodes.RemoveItemFromCart)]
- + [ClaptrapEventHandler(typeof(RemoveAllItemsFromCartEventHandler), ClaptrapCodes.RemoveAllItemsFromCart)]
- public class CartGrain : ClaptrapBoxGrain<CartState>, ICartGrain
- {
- public CartGrain(
- IClaptrapGrainCommonService claptrapGrainCommonService)
- : base(claptrapGrainCommonService)
- {
- }
- ....
ClaptrapEventHandlerAttribute
是框架定义的一个 Attribute,可以标记在 Grain 的实现类上,以实现 EventHandler 、 EventCode 和 ClaptrapGrain 三者之间的关联。
关联之后,如果在此 Grain 中产生的对应 EventCode 的事件将会由指定的 EventHandler 进行处理。
修改 Grain 接口
修改 Grain 接口的定义,才能够提供外部与 Claptrap 的互操作性。
打开 HelloClaptrap.IActors
项目的 ICartGrain
接口。
添加接口以及 Attribute。
- using System.Collections.Generic;
- using System.Threading.Tasks;
- using HelloClaptrap.Models;
- using HelloClaptrap.Models.Cart;
- using HelloClaptrap.Models.Cart.Events;
- using Newbe.Claptrap;
- using Newbe.Claptrap.Orleans;
- namespace HelloClaptrap.IActor
- {
- [ClaptrapState(typeof(CartState), ClaptrapCodes.CartGrain)]
- [ClaptrapEvent(typeof(AddItemToCartEvent), ClaptrapCodes.AddItemToCart)]
- [ClaptrapEvent(typeof(RemoveItemFromCartEvent), ClaptrapCodes.RemoveItemFromCart)]
- + [ClaptrapEvent(typeof(RemoveAllItemsFromCartEvent), ClaptrapCodes.RemoveAllItemsFromCart)]
- public interface ICartGrain : IClaptrapGrain
- {
- Task<Dictionary<string, int>> AddItemAsync(string skuId, int count);
- Task<Dictionary<string, int>> RemoveItemAsync(string skuId, int count);
- Task<Dictionary<string, int>> GetItemsAsync();
- + Task RemoveAllItemsAsync();
- }
- }
其中增加了两部分内容:
- 标记了
ClaptrapEvent
,使得事件与 Grain 进行关联。注意,这里与前一步的ClaptrapEventHandler
是不同的。此处标记的是 Event,上一步标记的是 EventHandler。 - 增加了 RemoveAllItemsAsync 方法,表示 “清空购物车” 的业务行为。需要注意的是 Grain 的方法定义有一定限制。详细可以参见《Developing a Grain》。
实现 Grain
接下来按照上一步的接口修改,来修改相应的实现类。
打开 HelloClaptrap.Actors
项目中的 Cart
文件夹下的 CartGrain
类。
添加对应的实现。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using HelloClaptrap.Actors.Cart.Events;
- using HelloClaptrap.IActor;
- using HelloClaptrap.Models;
- using HelloClaptrap.Models.Cart;
- using HelloClaptrap.Models.Cart.Events;
- using Newbe.Claptrap;
- using Newbe.Claptrap.Orleans;
- namespace HelloClaptrap.Actors.Cart
- {
- [ClaptrapEventHandler(typeof(AddItemToCartEventHandler), ClaptrapCodes.AddItemToCart)]
- [ClaptrapEventHandler(typeof(RemoveItemFromCartEventHandler), ClaptrapCodes.RemoveItemFromCart)]
- [ClaptrapEventHandler(typeof(RemoveAllItemsFromCartEventHandler), ClaptrapCodes.RemoveAllItemsFromCart)]
- public class CartGrain : ClaptrapBoxGrain<CartState>, ICartGrain
- {
- public CartGrain(
- IClaptrapGrainCommonService claptrapGrainCommonService)
- : base(claptrapGrainCommonService)
- {
- }
- + public Task RemoveAllItemsAsync()
- + {
- + if (StateData.Items?.Any() != true)
- + {
- + return Task.CompletedTask;
- + }
- +
- + var removeAllItemsFromCartEvent = new RemoveAllItemsFromCartEvent();
- + var evt = this.CreateEvent(removeAllItemsFromCartEvent);
- + return Claptrap.HandleEventAsync(evt);
- + }
- }
- }
增加了对接口方法的对应实现。需要注意的有以下几点:
一定要增加
if (StateData.Items?.Any() != true)
这行判断。因为这可以明显的减小存储的开销。事件在当执行
Claptrap.HandleEventAsync(evt)
便会持久化。而就此处的场景而言,如果购物车中原本就没有内容,清空或者持久化这个事件只是增加开销,而没有实际的意义。
因此,在此之前增加判断可以减小存储的无用消耗。一定要判断 State 以及传入参数是否满足事件执行的条件。
这与上一点所描述的内容侧重不同。上一点侧重表明 “不要产生没有意义的事件”,这一点表明 “绝不产生 EventHandler 无法消费的事件”。
在事件溯源模式中,业务的完成是以事件的持久化完成作为业务确定完成的依据。也就是说事件只要入库了,就可以认为这个事件已经完成了。
而在 EventHandler 中,只能接受从持久化层读出的事件。此时,按照事件的不可变性,已经无法再修改事件,因此一定要确保事件是可以被 EventHandler 消费的。所以,在Claptrap.HandleEventAsync(evt)
之前进行判断尤为重要。
因此,一定要实现单元测试来确保 Event 的产生和 EventHandler 的处理逻辑已经被覆盖。此处需要使用到一些 TAP 库中的一些方法,可以参见基于任务的异步模式
修改 Controller
前面的所有步骤完成之后,就已经完成了 Claptrap 的所有部分。但由于 Claptrap 无法直接提供与外部程序的互操作性。因此,还需要在在 Controller 层增加一个 API 以便外部进行 “清空购物车” 的操作。
打开 HelloClaptrap.Web
项目的 Controllers
文件夹下的 CartController
类。
- using System.Threading.Tasks;
- using HelloClaptrap.IActor;
- using Microsoft.AspNetCore.Mvc;
- using Orleans;
- namespace HelloClaptrap.Web.Controllers
- {
- [Route("api/[controller]")]
- public class CartController : Controller
- {
- private readonly IGrainFactory _grainFactory;
- public CartController(
- IGrainFactory grainFactory)
- {
- _grainFactory = grainFactory;
- }
- + [HttpPost("{id}/clean")]
- + public async Task<IActionResult> RemoveAllItemAsync(int id)
- + {
- + var cartGrain = _grainFactory.GetGrain<ICartGrain>(id.ToString());
- + await cartGrain.RemoveAllItemsAsync();
- + return Json("clean success");
- + }
- }
- }
小结
至此,我们就完成了 “清空购物车” 这个简单需求的所有内容。
您可以从以下地址来获取本文章对应的源代码:
最后但是最重要!
最近作者正在构建以反应式
、Actor模式
和事件溯源
为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap
本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。
联系方式:
- Github Issue
- Gitee Issue
- 公开邮箱 newbe-claptrap@googlegroups.com (发送到该邮箱的内容将被公开)
- Gitter
您还可以查阅本系列的其他选文:
- Newbe.Claptrap - 一套以 “事件溯源” 和 “Actor 模式” 作为基本理论的服务端开发框架
- 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
- 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
- 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
- docker-mcr 助您全速下载 dotnet 镜像
- Newbe.Claptrap 项目周报 1 - 还没轮影,先用轮跑
- Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
- Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
- Newbe.Claptrap 框架中为什么用 Claptrap 和 Minion 两个词?
GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap
Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap
您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 http://claptrap.newbe.pro。
- 本文作者: newbe36524
- 本文链接: https://www.newbe.pro/Newbe.Claptrap/Get-Started-2/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车的更多相关文章
- Newbe.Claptrap 框架入门,第二步 —— 创建项目
接上一篇 Newbe.Claptrap 框架入门,第一步 -- 开发环境准备 ,我们继续了解如何创建一个 Newbe.Claptrap 项目. Newbe.Claptrap 是一个用于轻松应对并发问题 ...
- Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存
接上一篇 Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开始学会添加一个全 ...
- 轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单
接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开 ...
- Newbe.Claptrap 框架入门,第一步 —— 开发环境准备
Newbe.Claptrap 框架依托于一些关键性的基础组件和一些可选的辅助组件.本篇我们来介绍一下如何准备一个开发环境. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如 ...
- Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
让我们来实现一个简单的 “电商购物车” 需求来了解一下如何使用 Newbe.Claptrap 进行开发. 业务需求 实现一个简单的 “电商购物车” 需求,这里实现几个简单的业务: 获取当前购物车中的商 ...
- 轻松应对并发问题,简易的火车票售票系统,Newbe.Claptrap 框架用例,第一步 —— 业务分析
Newbe.Claptrap 框架非常适合于解决具有并发问题的业务系统.火车票售票系统,就是一个非常典型的场景用例. 本系列我们将逐步从业务.代码.测试和部署多方面来介绍,如何使用 Newbe.Cla ...
- 轻松应对并发问题,Newbe.Claptrap 框架中 State 和 Event 应该如何理解?
Newbe.Claptrap 框架中 State 和 Event 应该如何理解?最近整理了一下项目的术语表.今天就谈谈什么是 Event 和 State. Newbe.Claptrap 是一个用于轻松 ...
- Newbe.Claptrap 框架如何实现多级生命周期控制?
Newbe.Claptrap 框架如何实现多级生命周期控制?最近整理了一下项目的术语表.今天就谈谈什么是 Claptrap Lifetime Scope. 特别感谢 kotone 为本文提供的校对建议 ...
- Newbe.Claptrap 框架如何实现 Claptrap 的多样性?
Newbe.Claptrap 框架如何实现 Claptrap 的多样性?最近整理了一下项目的术语表.今天就谈谈什么是 Claptrap Design 和 Claptrap Factory. 特别感谢 ...
随机推荐
- 定量度量程序复杂度的McCabe方法
[本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 请画出下面代码的 ...
- Java-CORBA
本文HelloCorba参考 Getting Started with JavaTM IDL 说在前面 Java TM IDL is a technology for distributed obje ...
- js语法基础入门(1.2)
1.4.查找元素的方法 1.4.1.查找元素的方法 JavaScript可以去操作html元素,要实现对html元素的操作,首选应该找到这个元素,有点类似于css中的选择器 html代码: <d ...
- 集合类List底层数据结构总结
数组: 1. 不安全 ArrayList 2. 安全 Vector链表LinkedList不安全 3.2.1 ArrayList 1. 适合随机查找和遍历,不适合删除和增加 2. 大小不足时,需要将已 ...
- 让对象拥有状态——C#中的状态模式
大家好,老胡又在博客和大家见面了,在聊今天的主角之前,老胡先给大家讲一个以前发生的故事. 真实的故事 当老胡还是小胡的时候,跟随团队一起开发一款游戏.这款游戏是一款末日生存类游戏,玩家可以 收集资 ...
- AspNetCore&Coding持续集成
对于现有很多持续集成工具来讲,功能越来越高级,使用了 Coding 有大半年时间,越发觉好用,特别是没钱续费服务器时,找到了新的羊毛. 一.众多持续集成工具 现在可用的持续集成工具繁多,各大云服务商都 ...
- JVM零碎知识
JVM常见XX参数 查看JVM默认值 常用基本配置参数 生产环境服务器变慢,如何诊断 生产环境CPU占用过高,如何诊断 JDK自带的JVM监控和性能分析工具 jps(虚拟机进程状况工具) jinfo( ...
- 调整数组顺序使奇数位于偶数前面(剑指offer-13)
方法1:新建两个数组,一个数组用来放奇数,一个数组用来放偶数,最后再把它们合并起来. 1 import java.util.*; 2 public class Solution { 3 public ...
- requests接口自动化3-url里带参数的get请求:params
url里带参数的get请求:用params传参 #2.带参数的get请求,把参数写成字典格式,用params传参 para2={"type":"math"} r ...
- java 面向对象(三十五):泛型在继承上的体现
泛型在继承上的体现: /* 1. 泛型在继承方面的体现 虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系. 补充:类A是类B的父类,A< ...