[Skr-Shop]购物车之架构设计
扫描二维码
随时随地手机看文章
skr shop是一群底层码农,由于被工作中的项目折磨的精神失常,加之由于程序员的自傲:别人设计的系统都是一坨shit,我的设计才是宇宙最牛逼,于是乎决定要做一个只设计不编码的电商设计手册。
说明
架构设计可以分为三个层面:
- 业务架构
- 系统架构
- 技术架构
业务架构
通过前面的需求分析,我们已经明确我们的购物车要干什么了。先来看一下一个典型的用户操作购物车过程。
上面这三个阶段,按照DDD中的概念,应该叫做实体,他们整体构成了购物车这个域;今天我们先不讲这些概念,就先略过,后面有机会单独发文讲解。
加车前
通过流程分析,我们总结出了系统需要具备的操作接口,以及这些接口对应的实体,现在我们先来看加车前主要要做些什么;
// 责任链的节点type RequestChain struct { Handler Next *RequestChain}
// 设置handlerfunc (h *RequestChain) SetNextHandler(in *RequestChain) *RequestChain { h.Next = in return in} 关于设计模式,大家可以看我小伙伴的github:https://github.com/TIGERB/easy-tips/tree/master/go/src/patterns
购物车
说完了加车前,现在来看购物车这一部分。我们在之前曾讨论过,购物车可能会有多种形态的,比如:存储多个商品一起结算,某个商品立即结算等。因此购物车一定会根据渠道来进行购物车类型的选择。
加入购物车
通过把条件验证的前置,会发现在进行加车操作时,这部分逻辑已经变得非常的轻量了。要做的主要是下面几个部分的逻辑。
注意:这里的添加并不是在购物车直接改数量,可能就是在列表、详情页直接添加添加。通过将合并后的购物车数据,通过营销活动检查确认ok后,直接回写到存储中。
合并购物车
为什么会有合并购物车这个操作?因为一般电商都是准许游客身份进行操作的,因此当用户登录后需要将二者进行合并。
购物车列表
购物车列表这是一个非常重要的接口,原则上购物车接口会提供两种类型,一种简版,一种完全版本;
结算
结算包括两部分,结算页的详情信息与提交订单。结算页可以说是在购物车列表上的一个包装,因为结算页与列表页最大的不同是需要用户选择配送地址(虚拟商品另说),此时会产生更明确的价格信息,其他基本一致。因此在设计购物车列表接口的时候,一定要考虑充分的通用性。
系统架构
系统结构主要包含,如何将业务架构映射过来,以及输出对应输入参数、输出参数的说明。由于输入、输出针对各自业务来确定的,而且没有什么难度,我们这里就只说如何将业务架构映射到系统架构,以及系统架构中最核心的Redis数据结构选择以及存储的数据结构设计。
代码结构
下面的代码目录是按照Golang来进行设计的。我们来看看如何将上面的业务架构映射到代码层面来。
为了保证内容的紧凑,我这里放弃了对整个微服务的目录介绍,只单独介绍了领域服务,后续会单独成文介绍下微服务的整个系统架构。通过上面的划分,我们完成了两件事情:
-
业务架构分析的结构在系统代码中都有映射,他们彼此体现。这样最大的好处是,保证设计与代码的一致性,看了文档你就知道对应的代码在哪里;
-
每个目录各自的关注点都进行了分离,更内聚,更容易开发与维护。
Redis存储
现在来看,我们选择Redis作为购物商品数据的存储,我们要解决两个问题,一是我们需要存哪些数据?二是我们用什么结构来存?
// 单个商品item元素type Item struct { ItemId string `json:"item_id"` ParentItemId string `json:"parent_item_id,omitempty"` // 绑定的父item id OrderId string `json:"order_id,omitempty"` // 绑定的订单号 Sku int64 `json:"sku"` Spu int64 `json:"spu"` Channel string `json:"channel"` Num int32 `json:"num"` Status int32 `json:"status"` TTL int32 `json:"ttl"` // 有效时间 SalePrice float64 `json:"sale_price"` // 记录加车时候的销售价格 SpecialPrice float64 `json:"special_price,omitempty"` // 指定价格加购物车 PostFree bool `json:"post_free,omitempty"` // 是否免邮 Activities []*ItemActivity `json:"activities,omitempty"` // 参加的活动记录 AddTime int64 `json:"add_time"` UpdateTime int64 `json:"update_time"`}
// 活动type ItemActivity struct { ActID string `json:"act_id"` ActType string `json:"act_type"` ActTitle string `json:"act_title"`} 重点说一下Item这个结构,item_id这个字段是标记购物车中某个商品的唯一标记,因为我们之前说过,同一个sku由于渠道不同,那么在购物车中会是两个不同的item;接下来的parent_item_id字段是用来标记父子关系的,这里将可能存在的树结构转成了顺序结构,我们不管是父商品还是子商品,都采用顺序存储,然后通过这个字段来进行关联;有些同学可能会奇怪,为什么会存order id这个字段呢?大家关注下自己的日常业务,比如:再来一单、定金预售等,这种一定是与某个订单相关联的,不管是为了资格验证还是数据统计。剩下的字段都是一些非常常规的字段,就不在一一介绍了;
字段的类型,大家根据自己的需要进行修改。接下来该说怎么选择Redis的存储结构了,Redis常用的Hash Table、集合、有序集合、链表、字符串五种,我们一个个来分析。
- 单个Value不能太大,要不然就会出现大key问题,所以一般购物车有上限限制,比如item不能超过多少个;
- 对redis的操作性能提升上来了,但是代码的就是修改单个item时的不便,必须每次读取全部然后找到对应的item进行修改;这里我们可以把从redis中的数据读取出来后,在内存中构建一个HashTable,来减少每次遍历的复杂度;
总结
至此对于购物车的实现设计算是完结了,其中关于订单表的设计会单独放到订单模块去讲。
- 改编版的责任链模式
- Redis的分布式事务锁实现