分享一下组内小伙伴张聪、庆红在年初对拍卖领域建模的总结
一、背景:原拍卖系统设计(基于数据模型)
原拍卖系统的构建了SkuBidRedisInfo(数据对象) 用来记录拍品在竞拍中的热点信息,并且将这个实体放入Redis中。
在拍品的生命周期中直接使用SkuBidRedisInfo进行逻辑判断,虽然通过SkuBidRedisInfo(拍品)和 AuctionSkuDTO(拍品)构造了 InnerSkuLotInfo (仅作为一个Value object)对象,但是InnerSkuLotInfo并不能完整的描述一个拍品完整的生命周期比如 竞拍前、竞拍中、竞拍结束、生成订单中等状态。
二、重构拍卖设计及改进过程
- 扩充数据对象 LotRedisInfo 信息,简化构造过程:
通过LotRedisInfo 存储拍品在竞拍中的信息,竞拍期间仅依赖LotRedisInfo即可构建拍品实体。一方面通过双写AuctionSkuDTO以及LotRedisInfo,保持竞拍中的数据一致性;另一方面简化了原有拍品实体构建的过程,得到更好的性能提升;
- 构建拍品领域实体 Lot ,定义了拍品的不同生命周期、以及各个生命周期的行为:
这里我们的初始做法是将Lot 的构建分为两种情况:1、竞拍中的拍品Lot,利用LotRedisInfo 进行构造 ,2、非竞拍中的拍品,从DB中取出数据构造Lot,放入Local&Redis缓存;
拍品生命周期内的行为
3.围绕实体进行数据变更:
当拍品信息发生变更时,比如开拍、出价、截拍、成交等行为发生,我们通过变更Lot,在基于Lot实体转化得到需要持久化的AuctionSkuDTO或LotRedisInfo,进行持久化;
持久化的过程如下:
变更Lot本身的信息
这里持久化Lot信息的时候,我们会根据Lot的预期截止时间和Lot对应 InnerActivityInfo 的预期截止时间比较判断是否需要延期活动截止时间,这里对 InnerActivityInfo 的信息进行修改并且持久化刷新缓存(潜在问题1)
持久化 Lot 信息 , Lot 导出相应的 AuctionSkuDTO(持久到DB) 、 活动未结束LotRedisInfo(持久到Redis)
- 新模型存在的问题
这里Lot领域实体的构建已经能够详细描述一个拍品的完整生命周期及各个行为,但是存在两个问题:
问题1 :InnerActivityInfo的修改不安全:
我们在构建Lot的时候,依赖了活动信息的实体InnerActivityInfo,但是 InnerActivityInfo 在Lot的整个生命周期里面并不是固定不变的,我们通过一个InnerActivityInfo A构建了一个Lot A,这个时候如果有其他Lot信息变更,导致InnerActivityInfo发生了变更,那么Lot A中使用了InnerActivityInfo A的部分信息就可能是脏数据了;
问题2 :由于引入了Lot在系统中存在了存储副本(缓存),对于Lot的读写存在了不一致:
应用实例A 获取拍品Lot 的本地缓存Lot A,然后对Lot A进行修改,比如修改拍品顺序,持久化,这个场景在单实例下没有问题,但是多实例下,如果在A操作之前,如果应用实例B上也想对同一个拍品Lot 进行修改,读取本地缓存Lot B修改了起拍价,持久化,那么应用实例A上读出来的数据就是脏数据,那么经过一系列操作,A只是想修改一个拍品顺序,但是却把B修改起拍价的数据给覆盖了,问题的关键就是我们不能基于缓存进行信息变更,再持久化到数据库;
解决方案:
问题1----解决方案:对象隔离
在Lot中我们不要强依赖InnerActivityInfo实体,而是每次使用时,传入一个最新的InnerActivityInfo(最佳做法是传递一个只读副本),这样就保证了在Lot的生命周期中,每次使用到InnerActivityInfo的相关信息都是最新的(这个是不是InnerActivityInfo的一致性问题由InnerActivityInfo保障,通过其领域服务进行对应的修改);
问题2----- 解决方案:CQRS,将拍品分为两种实体 ReadonlyLot(只读实体)、ReadWriteLot(可读写实体)
ReadonlyLot不包含对自身信息变更的能力,这样避免了误用。我们的内存cache和缓存都是建立在ReadonlyLot上的可能会读取到过期数据,但写场景是不允许过期数据的读取(写建立在读上,读过期,会产生写覆盖问题),通过实体层面的隔离我们杜绝了这种过期读的产生。 当我们需要修改Lot的信息时,即使我们获取了ReadonlyLot,也无法对其进行信息变更,从根本上杜绝了这种不安全发布的发生。
ReadonlyLot&ReadWriteLot
ReadonlyLot、ReadWriteLot的构建
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。