Day32-19|13.7.1 电子商务网站(网上商店 PetShop)案例分析

一、教材定位与学习目标

13.7.1 是第 13 章“层次式架构设计理论与实践”的第一个综合案例。它不是把前面讲过的表现层、中间层、数据访问层重新罗列一遍,而是要求我们把这些概念放进一个完整的电子商务业务中,观察层次式架构如何从需求出发,逐步形成可实现、可维护、可扩展的系统结构。

本节案例的价值主要有三点。第一,它把抽象的“分层”落实到商品浏览、购物车、订单、支付、库存和会员等具体业务。第二,它展示了表现层、业务逻辑层和数据访问层之间的职责边界,以及对象、接口、事务如何穿过这些边界。第三,它为案例分析题提供了一套稳定的答题语言:先描述业务场景,再识别质量属性,随后给出分层结构、关键组件、数据访问方式和风险控制措施。

学习本篇后,应能够回答以下问题:

  1. PetShop 这类网上商店为什么适合采用层次式架构?
  2. 一个订单从页面提交到数据库落库,分别经过哪些层和组件?
  3. 如何避免表现层直接访问数据库,或业务规则散落在页面代码中?
  4. 购物车、库存、订单和支付之间怎样划分事务边界?
  5. 当题目要求提高可维护性、可扩展性和安全性时,应该如何组织案例答案?

二、案例背景:网上商店到底要解决什么问题

一个典型的网上商店面向两类主要用户:普通顾客和后台运营人员。顾客需要注册登录、浏览商品、搜索和筛选、查看详情、加入购物车、提交订单、支付并查询物流;运营人员需要维护商品、分类、价格、库存、促销规则和订单状态。系统还要对接支付平台、物流平台、消息通知服务以及图片或文件存储服务。

如果采用“页面直接操作数据库”的方式,系统在早期可能能够快速上线,但随着业务增长会出现明显问题:同一条库存扣减规则可能被购物车、订单和后台接口分别实现;支付回调与页面提交可能重复更新订单;修改数据库结构会影响大量页面;更换数据库或增加缓存时,需要大范围修改业务代码。这些问题的根源不是代码量太大,而是职责没有被合理分离。

从架构视角看,网上商店至少包含以下业务能力:

业务域 主要职责 关键数据 典型质量关注点
会员 注册、登录、地址和权限 用户、地址、凭证 安全性、隐私性
商品 商品展示、分类、搜索 商品、规格、图片 可用性、响应时间
购物车 暂存购买意向、计算金额 购物车、购物项 一致性、并发性
订单 创建订单、状态流转 订单、明细、状态 正确性、可追踪性
库存 预占、扣减、释放 库存、流水 一致性、并发控制
支付 支付请求与回调 支付单、流水 安全性、幂等性
运营 商品和订单管理 配置、审计记录 可维护性、审计性

注意,业务域划分和层次划分不是同一个维度。业务域回答“系统做什么”,层次回答“这些职责以什么方式组织”。一个商品服务仍然可以包含表现、业务和数据访问三个层次;层次式架构强调的是依赖方向与职责边界,而不是把每一个业务域都拆成独立系统。

三、PetShop 的层次式总体结构

在考试中,可以先用下面的结构描述系统,再根据题目补充组件细节:

层次 主要组件 允许依赖 不应承担的职责
表现层 Web 页面、控制器、视图模型、参数校验 应用服务接口、认证组件 复杂业务规则、SQL 拼接
业务逻辑层 商品服务、订单服务、库存服务、支付协调器 领域对象、数据访问接口、外部服务接口 页面渲染、数据库连接细节
数据访问层 DAO、仓储、映射器、连接管理器 数据库驱动、缓存、消息基础设施 决定业务流程、处理页面请求
基础设施层 数据库、缓存、消息、日志、配置、对象存储 操作系统和网络 直接暴露给页面使用
外部系统适配层 支付适配器、物流适配器、通知适配器 外部 API 和协议 把第三方协议泄露到核心业务

依赖方向可以概括为:

用户请求 → 表现层 → 业务逻辑层 → 数据访问接口 → 数据访问实现 → 数据库
                              ↓
                        支付、物流、消息等外部适配器

这里最重要的不是层数,而是“上层通过稳定接口使用下层能力”。表现层不应知道订单表有哪些字段,业务层不应依赖某个页面控件,支付平台的签名格式不应扩散到订单核心逻辑中。

四、一次下单流程如何穿过各层

以用户提交订单为例,可以把流程拆解为以下步骤:

  1. 表现层接收用户选择的收货地址、商品明细和优惠券信息,完成格式校验、身份校验和防重复提交检查。
  2. 表现层调用订单应用服务,例如“创建订单”接口,并传入经过规范化的命令对象,而不是直接传入页面对象。
  3. 业务逻辑层重新查询商品价格、库存和促销规则。客户端传来的价格只能作为展示信息,不能作为最终结算依据。
  4. 订单服务计算订单明细金额、优惠金额、运费和应付金额,形成订单聚合或业务对象。
  5. 库存服务尝试预占库存。对于同一商品,需要使用数据库条件更新、版本号或其他并发控制策略避免超卖。
  6. 数据访问层在事务范围内保存订单、订单明细和库存流水。事务是否覆盖库存预占,要根据部署边界和一致性要求决定。
  7. 业务层调用支付适配器创建支付单,返回支付所需的跳转参数或二维码信息。
  8. 表现层只负责向用户展示支付结果。支付成功不能仅以浏览器返回为依据,最终状态应以可信的支付回调和对账结果为准。

可见,一个“点击提交”动作并不等于一个简单的数据库插入。案例分析时,应该写出验证、重算、库存、幂等和回调等关键控制点,才能体现架构设计能力。

五、表现层设计:页面只表达交互,不保存核心规则

表现层的职责是把用户操作转换为系统可理解的请求,再把业务结果转换为页面、接口或移动端可消费的表示。典型组成包括控制器、视图、视图模型、页面导航、会话管理和输入校验。

5.1 控制器与应用服务分离

控制器适合处理以下工作:解析请求、检查登录状态、完成基础格式校验、调用应用服务、选择返回视图或错误码。控制器不应该计算复杂折扣、不应该直接扣减库存,也不应该在其中拼装多张表的数据。

应用服务负责协调一个完整用例,例如“提交订单”“取消订单”“确认收货”。它可以调用多个领域服务和仓储接口,但仍然不应该包含 HTML 或页面控件操作。

5.2 防止过度暴露实体对象

直接把数据库实体作为页面模型,会把内部字段、关联关系和持久化结构暴露给用户界面。更稳妥的做法是定义输入模型和输出模型:输入模型只包含本次操作允许提交的字段,输出模型只返回页面真正需要的数据。这样既能减少误更新风险,也能降低数据库结构变化对前端的影响。

5.3 表现层的安全边界

登录认证、权限判断、CSRF 防护、输入编码、上传限制和敏感信息脱敏,应在表现层或统一安全组件中形成入口控制;但不能因此认为业务层无需校验。后台接口、定时任务和消息消费同样可能绕过页面,因此订单金额、库存数量和状态转换必须在业务层再次校验。

六、业务逻辑层设计:把“可变规则”集中起来

业务逻辑层是本案例的核心。它不只是“把数据库操作包一层”,而是要表达业务约束、流程协调和状态变化。

6.1 订单对象与状态机

订单可以具有待支付、已支付、待发货、已发货、已完成、已取消、退款中和已退款等状态。状态变化应受到明确约束,例如已取消订单不能再次支付,已发货订单不能无条件取消,支付回调重复到达不能重复增加余额或重复扣库存。

当前状态 允许事件 下一状态 关键检查
待支付 支付成功 已支付 支付单号、金额、签名、幂等键
待支付 超时取消 已取消 是否已支付、释放库存
已支付 商家发货 已发货 库存与物流信息
已发货 用户收货 已完成 物流状态、确认权限
已支付 申请退款 退款中 退款条件、原支付单
退款中 退款成功 已退款 回调验签、重复通知

把状态流转集中在订单领域服务中,可以避免不同入口随意修改状态。对于每个状态迁移,都应记录操作者、时间、来源和关联单号,便于审计和问题追踪。

6.2 价格与库存不能信任客户端

商品列表页展示的价格、数量和优惠金额都可能被修改。订单创建时,业务层应根据商品编号重新读取当前有效价格和促销配置,并检查商品是否可售、规格是否存在、库存是否足够。若系统需要“下单时锁价”,应将成交价固化在订单明细中,后续商品价格变化不能影响历史订单。

库存处理通常有预占库存、支付成功扣减和取消释放三种策略。选择哪一种,要看库存稀缺性、支付耗时和业务容忍度。无论采用哪种策略,都要记录库存变动流水,避免只更新一个数量字段而无法解释异常。

6.3 幂等性是跨系统流程的基础

浏览器重试、网络超时、消息重复投递和第三方回调重发都会造成重复请求。订单创建可以使用业务幂等键;支付回调可使用支付平台流水号作为唯一键;库存扣减应检查业务流水是否已经处理。幂等不是简单地“重复执行不报错”,而是多次执行最终结果与一次执行一致。

七、数据访问层设计:隔离持久化技术

数据访问层为业务层提供查询、保存和事务参与能力。它可以采用 DAO、仓储、数据映射器或 ORM 等方式,但考试答题时要说明其作用:隐藏数据库表结构、集中数据访问策略、管理连接与事务、支持缓存或读写分离,并让业务层依赖抽象接口。

PetShop 中可以将商品查询、订单读取、库存更新、会员信息分别封装。商品列表查询与订单聚合读取的性能特征不同,不宜为了“统一”而强行使用完全相同的对象和查询方式。读操作可以采用专用查询模型,写操作则通过业务对象和仓储完成约束检查。

事务边界需要特别说明。订单表、订单明细和库存流水如果位于同一数据库,可以在一个本地事务中保持原子性;支付平台和物流平台通常不在本地事务内,应采用状态机、可靠消息、补偿操作和对账机制解决最终一致性,而不能假设远程调用可以回滚本地事务。

连接管理方面,应使用连接池、合理的超时和资源释放策略,避免每次请求建立新连接,也避免连接泄漏。慢查询、锁等待和事务持续时间应纳入监控;在高并发场景下,还需要考虑读写分离后的读延迟对订单查询和库存判断的影响。

八、案例中的质量属性与架构权衡

8.1 可维护性

层次式结构通过职责分离降低修改传播范围。页面样式变化主要影响表现层,数据库字段变化主要由数据访问层和映射模型吸收,促销规则变化集中在业务层。但分层会增加接口和对象数量,因此要避免为了形式而层层转发,接口应围绕稳定的业务能力设计。

8.2 可扩展性

商品搜索、图片访问和热门商品查询可通过缓存或搜索组件扩展;订单写入可通过消息和异步任务削峰;支付、物流和通知通过适配器支持多供应商切换。扩展设计的关键是先确定变化点,再隔离变化,而不是盲目增加抽象层。

8.3 性能

性能优化需要结合访问模式。商品浏览通常读多写少,适合缓存和静态资源加速;下单和库存扣减要求正确性优先,应控制事务范围并减少锁竞争;后台报表可以采用异步汇总或分析库,避免影响交易库。

8.4 安全与可靠性

系统应使用安全传输、密码安全存储、最小权限、支付验签、敏感数据脱敏和审计日志。可靠性方面,需要对数据库、缓存、消息和外部支付依赖设置超时、重试上限、熔断或降级,并建立失败补偿和人工处理通道。

九、案例分析题答题模板

遇到“设计一个网上商店的系统架构”类题目,可以按以下顺序作答:

  1. 需求与质量属性:列出商品、订单、库存、支付等核心功能,并指出可维护性、可扩展性、性能、安全性和可靠性要求。
  2. 总体架构:给出表现层、业务逻辑层、数据访问层、基础设施及外部适配器,说明依赖方向。
  3. 关键流程:选择下单或支付回调,说明请求经过的组件、校验点、事务边界和异常处理。
  4. 核心设计:补充订单状态机、库存并发控制、幂等、缓存、消息和适配器设计。
  5. 权衡与风险:说明分层带来的维护收益和调用开销,并给出监控、日志、审计与补偿措施。

十、课后练习

练习一:选择题

在网上商店中,以下做法最符合层次式架构职责分离原则的是( )。

A. 页面根据提交参数直接拼接 SQL 更新库存

B. 控制器从数据库读取商品价格并计算最终应付金额

C. 订单服务重新读取价格和库存,通过数据访问接口完成下单

D. 支付平台的验签代码复制到每个页面中

答案:C。 价格和库存属于核心业务约束,应在业务逻辑层重新确认;数据访问细节通过接口隔离;支付验签应由统一适配器或安全组件完成。

练习二:简答题

请说明为什么支付成功后不能只依赖浏览器同步跳转来修改订单状态。

参考答案: 浏览器可能被关闭、篡改或因网络问题未能返回;同步跳转不一定能证明支付平台已经完成扣款。系统应以支付平台的可信回调、主动查询和对账结果为依据,完成签名校验、金额校验、订单匹配和幂等处理,再推进订单状态。回调失败时可通过重试队列或定时补偿恢复。

练习三:案例题

某网上商店在促销期间出现超卖和重复扣款。请从架构角度分析问题并提出改进方案。

参考答案: 超卖可能源于表现层提交数量被直接信任、库存读取与扣减之间缺少并发控制,或库存预占和取消释放没有统一流水。应在业务层重新校验商品和数量,使用条件更新、乐观锁或串行化策略控制并发,建立库存预占、扣减、释放流水,并将订单号作为幂等关联键。重复扣款可能源于支付回调重复处理,应以第三方支付流水号建立唯一约束,回调先验签、核对金额和订单,再检查处理记录;必要时通过状态机限制非法迁移。最终还要增加库存告警、支付对账、审计日志和异常补偿。

十一、论文与综合知识迁移

本案例可以直接转化为论文素材。例如以“论层次式架构在电子商务系统中的应用”为题,摘要中说明项目背景、业务规模和主要矛盾;正文第一部分写需求与质量属性,第二部分写分层结构和关键流程,第三部分写订单状态、库存并发、支付幂等和数据访问设计,第四部分写上线效果、问题与改进。

写作时不要只写“系统采用三层架构,效果良好”。高级考试更看重决策依据和问题闭环:为什么采用分层,哪一处变化被隔离,哪个质量属性因此得到改善,实施过程中遇到什么问题,又如何通过事务、缓存、消息、监控或补偿机制解决。案例中的 PetShop 不是背诵名词,而是训练从业务问题推导架构方案的练习。

十二、今日复盘清单

  • 我能否解释业务域划分与架构分层的区别?
  • 我能否画出一次下单请求经过的层次与组件?
  • 我是否明确价格、库存和订单状态必须由业务层最终校验?
  • 我能否说明本地事务与跨系统最终一致性的边界?
  • 我能否用“问题—方案—质量属性—验证结果”写出一段案例答案?

本节的核心结论是:层次式架构的价值不在于把代码机械地分成几层,而在于建立稳定的职责边界,使业务规则集中、持久化技术可替换、外部系统可隔离,并让性能、安全、可靠性等质量属性能够被明确设计和验证。