第3章 质量属性场景 (Quality Attribute Scenarios)
一、导读
1.1 本章学习目标
在本章结束时,你将能够:
- 理解质量属性场景的本质,区分场景与模糊需求的差异
- 掌握场景六要素,准确描述刺激源、刺激、环境、制品、响应和响应度量
- 构建有效场景,将抽象的质量需求转化为具体可验证的场景
- 使用场景进行沟通,在利益相关者之间建立共同理解
- 评估场景质量,识别不完整或模糊的场景描述
- 应用场景进行架构评估,用场景驱动架构决策
- 精化场景粒度,从高层场景逐步细化到具体实现
- 避免场景陷阱,识别常见的场景设计错误
1.2 为什么本章如此重要
质量属性场景是软件架构领域最重要的实践方法之一。它解决了质量需求描述中的一个根本难题:如何将模糊的、主观的质量期望转化为具体的、可验证的技术要求。
在实际项目中,我们经常听到这样的需求:"系统要快"、"系统要安全"、"系统要稳定"。这些表述虽然表达了用户的期望,但对于架构师来说远远不够。多快算快?什么样的安全才算安全?稳定是指不宕机还是指数据不丢失?
质量属性场景提供了一种结构化的方法来精化这些模糊需求。通过定义具体的刺激、环境和响应,场景将抽象的质量概念转化为可以讨论、分析和测试的具体情境。
场景的重要性体现在以下几个方面:
首先,场景是架构设计的输入。没有清晰的场景,架构师无法做出有针对性的设计决策。场景告诉架构师系统需要在什么条件下应对什么挑战,以及什么样的响应是可以接受的。
其次,场景是架构评估的基础。ATAM(架构权衡分析方法)等评估技术都依赖于场景来检验架构是否满足质量要求。通过执行场景,可以发现架构的弱点和风险。
第三,场景是沟通的桥梁。业务人员、开发人员、测试人员和运维人员对质量的理解各不相同。场景提供了一种共同的语言,使各方能够在具体的情境下讨论质量需求。
最后,场景是验收的标准。当项目完成时,如何判断系统是否达到了预期的质量水平?场景提供了可执行的测试用例,使质量验收变得客观和可操作。
本章将深入讲解质量属性场景的构造方法、使用技巧和最佳实践。掌握这一方法,将使你的架构设计更加精准和有效。
1.3 核心问题引导
在阅读本章时,请思考以下问题:
为什么"系统响应时间小于 2 秒"不是一个完整的质量属性场景?
质量属性场景的六个要素分别是什么?缺少任何一个会有什么后果?
如何从一个模糊的质量需求(如"系统要可靠")推导出多个具体场景?
场景的粒度应该如何控制?太粗或太细分别有什么问题?
场景与用例(Use Case)有什么区别和联系?
如何在资源有限的情况下确定场景的优先级?
场景应该在项目的哪个阶段创建?由谁来创建?
场景如何与架构决策建立明确的关联?
二、核心概念详解
3.1 质量属性场景的定义
严格定义:
质量属性场景(Quality Attribute Scenario)是一种结构化的方法,用于精确描述系统在特定条件下对特定刺激的预期响应。它由六个要素组成:刺激源(Source of Stimulus)、刺激(Stimulus)、环境(Environment)、制品(Artifact)、响应(Response)和响应度量(Response Measure)。
通俗地说,质量属性场景就是一个"如果...那么..."的故事,但它比日常语言中的条件句更加精确和完整。它不仅说明发生了什么和系统如何反应,还说明了在什么情况下发生、影响系统的哪些部分、以及如何衡量反应的好坏。
场景 vs 普通需求
让我们对比两种描述方式:
普通需求描述:
"系统应该具有高可用性。"
这种描述的问题:
- "高"是多高?99% 还是 99.99%?
- 在什么情况下需要高可用?维护期间呢?
- 如果不可用,允许中断多久?
- 如何验证系统是否达到了"高可用"?
质量属性场景描述:
"当生产环境的数据库服务器发生故障时(刺激源+刺激+环境),订单处理服务(制品)应在 30 秒内自动切换到备用数据库(响应),切换过程中已提交的订单不得丢失,未完成的订单处理延迟不超过 5 秒(响应度量)。"
这种描述的优势:
- 明确了触发条件(数据库故障)
- 明确了影响范围(订单处理服务)
- 明确了预期行为(自动切换)
- 明确了衡量标准(30 秒内、无数据丢失、延迟不超过 5 秒)
- 可以设计测试来验证
场景的形式化表示
质量属性场景可以用以下模板表示:
【质量属性类型】场景名称
刺激源:<谁或什么产生了刺激>
刺激:<发生了什么事件>
环境:<在什么条件下发生>
制品:<系统的哪些部分受到影响>
响应:<系统如何应对>
响应度量:<如何衡量响应的有效性>这个模板确保了场景的完整性和可验证性。
3.2 场景六要素详解
质量属性场景的六个要素缺一不可,每个要素都有其特定的作用。
1. 刺激源(Source of Stimulus)
刺激源是产生刺激的实体。它可以是:
- 人:用户、管理员、攻击者
- 系统:外部系统、内部组件、第三方服务
- 环境:定时器、传感器、操作系统
- 自然:自然灾害、电力中断
示例:
- "终端用户发起支付请求"
- "监控系统检测到 CPU 使用率超过 90%"
- "定时任务触发数据备份"
- "黑客尝试 SQL 注入攻击"
为什么刺激源重要?
不同的刺激源可能代表不同的信任级别和威胁模型。来自内部用户的请求和来自外部攻击者的请求需要不同的处理策略。明确刺激源有助于设计适当的安全和控制机制。
2. 刺激(Stimulus)
刺激是到达系统的事件或条件变化。它可以是:
- 正常事件:用户请求、定时任务、数据更新
- 异常事件:硬件故障、网络中断、恶意攻击
- 负载变化:流量激增、数据量增长
- 配置变更:参数调整、功能开关
示例:
- "用户提交订单"
- "主数据库连接失败"
- "并发用户数从 1000 突增到 10000"
- "SSL 证书即将过期"
刺激的分类
根据性质,刺激可以分为:
- 预期刺激:系统设计时考虑到的正常情况
- 意外刺激:未预料到的异常情况
- 良性刺激:不会损害系统的变化
- 恶意刺激:试图破坏系统的行为
完整的场景集应该覆盖各种类型的刺激,特别是那些可能导致系统失败的意外和恶意刺激。
3. 环境(Environment)
环境是刺激发生时的系统状态和外部条件。它包括:
- 运行模式:正常模式、启动模式、降级模式、恢复模式
- 负载条件:低负载、正常负载、峰值负载、过载
- 资源状态:资源充足、资源紧张、资源耗尽
- 外部条件:网络状况、依赖服务状态、时间约束
示例:
- "系统在正常负载下运行"
- "系统刚完成部署,正在预热"
- "系统处于降级模式,非核心功能已关闭"
- "网络连接不稳定,丢包率达到 5%"
为什么环境重要?
同样的刺激在不同环境下可能有完全不同的含义和处理方式。例如,数据库故障在正常负载下可能可以通过重试解决,但在峰值负载下可能需要立即切换到备用系统。
4. 制品(Artifact)
制品是受刺激影响的系统部分。它可以是:
- 整个系统:刺激影响系统的所有部分
- 子系统/模块:刺激只影响特定模块
- 组件/服务:刺激只影响某个服务
- 数据:刺激只影响数据存储或处理
- 接口:刺激只影响 API 或通信协议
示例:
- "订单处理服务"
- "用户认证模块"
- "支付网关接口"
- "用户会话数据"
制品的粒度
制品的粒度应该与场景的目的相匹配:
- 架构级场景:制品通常是子系统或主要组件
- 设计级场景:制品可以是模块或类
- 实现级场景:制品可以是函数或数据结构
在架构设计阶段,我们主要关注架构级场景。
5. 响应(Response)
响应是系统对刺激采取的行动。它应该描述:
- 检测:系统如何感知刺激的发生
- 处理:系统执行什么操作来应对
- 结果:处理后系统处于什么状态
示例:
- "记录错误日志并发送告警通知"
- "切换到备用数据库并重试失败的请求"
- "拒绝新请求并返回友好的错误页面"
- "启动额外的服务实例以应对负载增长"
响应的完整性
一个好的响应描述应该包含:
- 即时响应:刺激发生后立即采取的行动
- 后续响应:长期或延迟采取的行动
- 恢复行动:系统如何恢复到正常状态
例如,对于数据库故障场景:
- 即时响应:检测到连接失败,记录错误
- 后续响应:切换到备用数据库,重试失败的操作
- 恢复行动:修复主数据库,重新同步数据,切换回主数据库
6. 响应度量(Response Measure)
响应度量定义了如何评估响应的有效性。它应该是:
- 可测量的:有明确的数值或布尔判断
- 可验证的:可以通过测试或监控来验证
- 有意义的:与业务目标或用户体验相关
示例:
- "切换时间在 30 秒以内"
- "无数据丢失"
- "99% 的用户请求不受影响"
- "CPU 使用率降至 70% 以下"
度量的类型
响应度量可以分为:
- 时间度量:响应时间、恢复时间、处理时间
- 数量度量:数据丢失量、受影响用户数、错误率
- 质量度量:数据一致性级别、服务降级程度
- 资源度量:CPU 使用率、内存占用、带宽消耗
度量与 SLA/SLO
响应度量通常与服务等级协议(SLA)或服务等级目标(SLO)相关联。SLA 是对用户的承诺,SLO 是内部的目标。场景的响应度量应该与这些业务指标保持一致。
3.3 场景的构造方法
构造高质量的质量属性场景需要遵循一定的方法和步骤。
步骤一:识别关键质量属性
首先,确定系统最关键的质量属性。这可以通过以下方式:
- 利益相关者访谈:了解各方的质量期望
- 业务分析:识别支撑业务目标的关键质量
- 竞品分析:参考同类系统的质量表现
- 历史经验:从过往项目中总结教训
通常,一个系统有 3-5 个最关键的质量属性。不要试图为所有质量属性都编写场景,聚焦最重要的几个。
步骤二:头脑风暴刺激
针对每个质量属性,头脑风暴可能的刺激。可以使用以下技巧:
- 正常路径:系统正常使用时的刺激
- 异常路径:出错时的刺激
- 边界条件:极限情况下的刺激
- 恶意行为:攻击者的刺激
例如,对于性能属性:
- 正常:用户发起常规请求
- 异常:依赖服务响应缓慢
- 边界:并发用户数达到设计上限
- 恶意:DDoS 攻击
步骤三:定义环境和制品
为每个刺激确定典型的环境条件和影响的系统部分。考虑:
- 系统在什么运行模式下?
- 负载水平如何?
- 资源状况怎样?
- 哪些组件会受到影响?
步骤四:描述响应和度量
定义系统应该如何响应,以及如何衡量响应的有效性。确保:
- 响应是具体的行动,而非模糊的描述
- 度量是可验证的数值或条件
- 度量与业务价值相关
步骤五:审查和完善
审查场景的完整性和质量:
- 六要素是否齐全?
- 描述是否清晰无歧义?
- 度量是否可验证?
- 场景是否现实可行?
场景构造示例
让我们为一个电商系统构造几个场景。
场景 1:性能场景
【性能】高峰期订单提交
刺激源:终端用户
刺激:提交订单请求
环境:促销活动期间,系统负载为正常水平的 5 倍
制品:订单创建服务及其依赖的库存服务和支付服务
响应:系统接受订单请求,异步处理库存扣减和支付,立即返回订单确认
响应度量:P95 响应时间 < 3 秒,订单创建成功率 > 99.5%场景 2:可用性场景
【可用性】支付服务故障
刺激源:第三方支付网关
刺激:支付服务返回超时错误
环境:正常运营时段,系统负载正常
制品:订单支付模块
响应:系统记录错误,向用户显示友好提示,提供稍后重试选项,后台定时重试未完成的支付
响应度量:用户可见的错误信息清晰易懂,后台重试成功率 > 95%,最终支付完成率 > 98%场景 3:安全性场景
【安全性】SQL 注入攻击
刺激源:恶意攻击者
刺激:在搜索框中输入 SQL 注入代码
环境:系统正常运行,WAF(Web 应用防火墙)已启用
制品:商品搜索功能及其数据库查询层
响应:系统检测并阻止恶意输入,记录安全事件,不暴露任何数据库信息,向安全团队发送告警
响应度量:注入尝试 100% 被阻止,无敏感信息泄露,告警在 5 分钟内发出场景 4:可修改性场景
【可修改性】添加新的支付方式
刺激源:产品经理
刺激:要求集成新的支付渠道(如数字货币)
环境:系统已上线运营,有稳定的用户群
制品:支付模块及其接口定义
响应:开发人员只需实现新的支付适配器并配置,无需修改现有支付逻辑,经过测试后可独立部署
响应度量:开发工作量 < 5 人天,不影响现有支付功能,部署无需停机3.4 场景的分类
质量属性场景可以根据不同的维度进行分类。
按质量属性分类
这是最常见的分类方式:
- 性能场景:关注响应时间、吞吐量
- 可用性场景:关注故障处理和恢复
- 安全性场景:关注威胁防护
- 可修改性场景:关注变更的难易程度
- 可测试性场景:关注测试的支持程度
- 可部署性场景:关注部署的便利性
按刺激类型分类
- 正常刺激场景:系统预期会收到的刺激
- 异常刺激场景:错误或故障导致的刺激
- 恶意刺激场景:攻击者故意产生的刺激
按生命周期阶段分类
- 运行时场景:系统运行时发生的场景
- 开发时场景:开发和测试期间的场景
- 部署时场景:部署和升级期间的场景
按粒度分类
- 系统级场景:涉及整个系统
- 子系统级场景:涉及某个子系统
- 组件级场景:涉及单个组件
按优先级分类
- 关键场景:必须满足,否则系统不可接受
- 重要场景:应该满足,对系统价值有较大影响
- 期望场景:最好满足,但不是必须的
3.5 场景的精化与演化
场景不是一次性产物,而是随着项目进展不断精化和演化的。
场景精化的层次
Level 1: 业务愿景
"系统需要高性能"
Level 2: 质量属性目标
"订单提交响应时间 < 2 秒"
Level 3: 质量属性场景
"当用户在正常负载下提交订单时,订单服务应在 2 秒内返回确认"
Level 4: 具体测试用例
"使用 JMeter 模拟 1000 并发用户提交订单,测量 P95 响应时间"从 Level 1 到 Level 4,场景越来越具体,越来越可验证。
场景与架构决策的关联
每个场景应该能够追溯到具体的架构决策:
场景:订单提交响应时间 < 2 秒
↓
架构决策:
- 采用异步处理模式
- 引入 Redis 缓存热点数据
- 数据库读写分离
- 服务水平扩展至 3 个实例这种关联使得架构决策有据可依,也使得场景有了实现的保障。
场景的演化
随着项目的推进,场景可能会:
- 新增:发现新的质量需求
- 修改:业务变化导致需求调整
- 废弃:某些场景不再相关
- 拆分:大场景拆分为更小的子场景
- 合并:相似场景合并以减少冗余
场景应该像代码一样进行版本管理,记录变更历史和理由。
3.6 场景的优先级排序
资源总是有限的,不可能对所有场景投入同等的精力。需要对场景进行优先级排序。
优先级评估维度
业务价值:场景对业务目标的重要程度
风险程度:场景不满足的后果严重性
实现难度:满足场景所需的工作量
发生频率:场景触发的频繁程度
优先级矩阵
可以使用二维矩阵进行排序:
高业务价值 低业务价值
高发生频率 优先实现 计划实现
低发生频率 谨慎评估 可选实现MoSCoW 方法
另一种常用的优先级分类方法:
- Must have:必须有,否则系统不可接受
- Should have:应该有,对系统很重要
- Could have:可以有,如果资源允许
- Won't have:本次不会有,留待未来
场景优先级示例
对于一个电商系统:
| 场景 | 业务价值 | 发生频率 | 优先级 |
|---|---|---|---|
| 订单提交性能 | 高 | 高 | Must |
| 支付服务故障恢复 | 高 | 低 | Must |
| SQL 注入防护 | 高 | 低 | Must |
| 添加新支付方式 | 中 | 低 | Should |
| 报表生成性能 | 低 | 低 | Could |
三、程序员的视角
3.1 在架构设计中的应用
质量属性场景在日常开发中有多种应用场景。
场景一:需求澄清
当产品经理提出模糊的质量需求时,使用场景进行澄清:
产品经理:"系统要快。"
架构师:"能具体一点吗?让我用场景来确认:
- 谁发起请求?普通用户还是管理员?
- 什么类型的请求?浏览商品还是提交订单?
- 在什么负载下?平时还是促销期间?
- 多快算快?2 秒还是 5 秒?
- 多少比例的请求需要满足这个速度?95% 还是 99%?"
通过这些问题,将模糊需求转化为具体场景。场景二:技术方案评估
当比较不同技术方案时,用场景作为评估标准:
方案 A:使用关系型数据库
方案 B:使用 NoSQL 数据库
评估场景:
1. 高并发读取场景:10000 QPS,P95 < 100ms
- 方案 A:需要复杂的缓存层,难以保证
- 方案 B:原生支持高并发,容易满足
→ 方案 B 胜出
2. 复杂查询场景:多表关联聚合
- 方案 A:SQL 原生支持,简单高效
- 方案 B:需要应用层组装,复杂且慢
→ 方案 A 胜出
综合权衡后做出选择。场景三:代码审查
从场景角度审查代码:
审查点:这段代码是否满足性能场景?
- 是否有不必要的循环或查询?
- 是否正确使用了缓存?
- 是否有潜在的 N+1 查询问题?
审查点:这段代码是否满足可用性场景?
- 是否有适当的异常处理?
- 是否有重试机制?
- 失败时是否有降级策略?场景四:测试设计
将场景转化为测试用例:
场景:支付服务故障时,订单仍能创建
测试用例:
1. Mock 支付服务返回超时
2. 提交订单请求
3. 验证订单创建成功,状态为"待支付"
4. 验证用户收到友好提示
5. 验证后台有重试任务创建3.2 常见陷阱与误区
陷阱一:场景过于模糊
表现:场景描述中使用"快速"、"稳定"、"安全"等模糊词汇,没有具体度量。
后果:
- 无法验证是否满足
- 不同人有不同理解
- 验收时产生争议
避免方法:
- 每个场景必须包含响应度量
- 度量必须是可测量的数值
- 与利益相关者确认度量的合理性
陷阱二:场景过于理想化
表现:场景假设所有条件都是最优的,不考虑异常情况。
例如:
- "在网络正常的情况下,请求应在 1 秒内响应"
- "在所有依赖服务都可用时,系统应正常工作"
后果:
- 忽略了真正的挑战
- 无法发现架构弱点
- 生产环境中频繁出问题
避免方法:
- 重点设计异常场景
- 考虑依赖故障、网络问题、资源限制
- 模拟真实的生产环境条件
陷阱三:场景与实际脱节
表现:场景是在会议室里凭空想象的,没有基于真实的业务需求和技术约束。
后果:
- 场景不能反映真实需求
- 架构决策偏离实际
- 资源浪费在不重要的场景上
避免方法:
- 与真实用户和业务方沟通
- 分析生产环境的日志和监控数据
- 参考行业基准和竞品表现
陷阱四:场景过多过细
表现:为每个小细节都编写场景,导致场景数量庞大。
后果:
- 管理和维护成本高
- 重点不突出
- 团队疲于应付
避免方法:
- 聚焦关键质量属性
- 每个属性 3-5 个代表性场景
- 定期清理过时场景
陷阱五:场景一次性使用
表现:场景只在设计阶段创建,之后就不再更新和使用。
后果:
- 场景与实际系统脱节
- 场景失去指导意义
- 知识未能传承
避免方法:
- 将场景纳入文档体系
- 在迭代中持续更新场景
- 用场景指导测试和验收
3.3 与其他层级的联系
与业务战略的联系
场景将业务目标转化为技术要求:
业务目标:提升用户满意度
↓
质量目标:提高系统响应速度
↓
场景:P95 页面加载时间 < 2 秒
↓
架构决策:CDN 加速、前端优化、后端缓存与项目管理的联系
场景影响项目计划和资源分配:
- 估算:满足场景需要的工作量
- 排期:高优先级场景先实现
- 风险:难以满足的场景是高风险项
- 验收:场景是验收的依据
与测试的联系
场景直接指导测试设计:
- 测试用例:每个场景对应多个测试用例
- 测试数据:场景定义了测试的条件和数据
- 验收标准:场景的响应度量就是验收标准
- 自动化:场景可以转化为自动化测试脚本
与运维的联系
场景帮助运维团队理解系统行为:
- 监控指标:场景的响应度量成为监控指标
- 告警阈值:场景定义了何时需要告警
- 应急预案:场景描述了故障时的预期行为
- 容量规划:场景提供了负载基准
四、代码实战
4.1 示例一:场景驱动的架构设计
下面我们用 Java 展示如何根据质量属性场景设计系统架构。
/**
* 场景驱动的订单系统设计
*
* 关键场景:
* 1. 性能场景:高峰期订单提交 P95 < 2 秒
* 2. 可用性场景:支付服务故障时订单仍能创建
* 3. 安全性场景:防止重复提交订单
*/
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
// ==================== 领域模型 ====================
class Order {
private final String orderId;
private final String userId;
private final List<OrderItem> items;
private OrderStatus status;
private final long createdAt;
public Order(String orderId, String userId, List<OrderItem> items) {
this.orderId = orderId;
this.userId = userId;
this.items = items;
this.status = OrderStatus.CREATED;
this.createdAt = System.currentTimeMillis();
}
// Getters
public String getOrderId() { return orderId; }
public String getUserId() { return userId; }
public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
public OrderStatus getStatus() { return status; }
public long getCreatedAt() { return createdAt; }
public void setStatus(OrderStatus status) {
this.status = status;
}
@Override
public String toString() {
return "Order{" +
"orderId='" + orderId + '\'' +
", userId='" + userId + '\'' +
", status=" + status +
", createdAt=" + createdAt +
'}';
}
}
class OrderItem {
private final String productId;
private final int quantity;
private final double price;
public OrderItem(String productId, int quantity, double price) {
this.productId = productId;
this.quantity = quantity;
this.price = price;
}
public String getProductId() { return productId; }
public int getQuantity() { return quantity; }
public double getPrice() { return price; }
}
enum OrderStatus {
CREATED, // 已创建
PAYING, // 支付中
PAID, // 已支付
FAILED, // 失败
CANCELLED // 已取消
}
// ==================== 场景一:性能优化 - 异步处理 ====================
/**
* 异步订单处理器
*
* 场景:高峰期订单提交 P95 < 2 秒
* 策略:接收请求后立即返回,后台异步处理库存和支付
*/
class AsyncOrderProcessor {
private final ExecutorService executor;
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final PaymentService paymentService;
private final AtomicLong orderCounter;
public AsyncOrderProcessor(
OrderRepository orderRepository,
InventoryService inventoryService,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.inventoryService = inventoryService;
this.paymentService = paymentService;
this.orderCounter = new AtomicLong(0);
// 使用线程池进行异步处理
this.executor = Executors.newFixedThreadPool(10);
}
/**
* 创建订单(快速返回)
*
* 响应度量:P95 响应时间 < 2 秒
*/
public CompletableFuture<Order> createOrder(String userId, List<OrderItem> items) {
String orderId = generateOrderId();
Order order = new Order(orderId, userId, items);
// 立即保存订单状态为 CREATED
orderRepository.save(order);
// 异步处理后续流程
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
processOrderAsync(order);
} catch (Exception e) {
handleOrderFailure(order, e);
}
}, executor);
// 立即返回订单,不等待异步处理完成
return CompletableFuture.completedFuture(order);
}
private void processOrderAsync(Order order) {
// 步骤 1:检查并扣减库存
boolean stockReserved = inventoryService.reserveStock(
order.getItems().stream()
.map(item -> new InventoryReservation(item.getProductId(), item.getQuantity()))
.toList()
);
if (!stockReserved) {
order.setStatus(OrderStatus.FAILED);
orderRepository.update(order);
return;
}
// 步骤 2:发起支付
order.setStatus(OrderStatus.PAYING);
orderRepository.update(order);
boolean paymentSuccess = paymentService.processPayment(
order.getOrderId(),
order.getItems().stream().mapToDouble(OrderItem::getPrice).sum()
);
if (paymentSuccess) {
order.setStatus(OrderStatus.PAID);
} else {
order.setStatus(OrderStatus.FAILED);
// 释放库存
inventoryService.releaseStock(order.getItems().stream()
.map(item -> new InventoryReservation(item.getProductId(), item.getQuantity()))
.toList());
}
orderRepository.update(order);
}
private void handleOrderFailure(Order order, Exception e) {
order.setStatus(OrderStatus.FAILED);
orderRepository.update(order);
// 记录错误日志,发送告警
System.err.println("Order processing failed: " + order.getOrderId() + ", error: " + e.getMessage());
}
private String generateOrderId() {
return "ORD-" + System.currentTimeMillis() + "-" + orderCounter.incrementAndGet();
}
public void shutdown() {
executor.shutdown();
}
}
// ==================== 场景二:可用性 - 熔断降级 ====================
/**
* 带熔断器的支付服务客户端
*
* 场景:支付服务故障时订单仍能创建
* 策略:支付服务不可用时,订单状态设为"待支付",后台重试
*/
class ResilientPaymentService implements PaymentService {
private final PaymentService realPaymentService;
private final CircuitBreaker circuitBreaker;
public ResilientPaymentService(PaymentService realPaymentService) {
this.realPaymentService = realPaymentService;
this.circuitBreaker = new CircuitBreaker(5, 30); // 5 次失败后熔断,30 秒后重试
}
@Override
public boolean processPayment(String orderId, double amount) {
try {
return circuitBreaker.execute(() -> realPaymentService.processPayment(orderId, amount));
} catch (CircuitBreakerOpenException e) {
// 熔断器打开,记录订单为待支付,稍后重试
System.out.println("Payment service unavailable, order " + orderId + " marked as pending");
return false; // 返回 false 但不失败订单
}
}
}
class CircuitBreaker {
private final int failureThreshold;
private final long retryDelayMs;
private int failureCount;
private long lastFailureTime;
private boolean isOpen;
public CircuitBreaker(int failureThreshold, long retryDelaySeconds) {
this.failureThreshold = failureThreshold;
this.retryDelayMs = retryDelaySeconds * 1000;
this.failureCount = 0;
this.isOpen = false;
}
public synchronized <T> T execute(Callable<T> callable) throws Exception {
if (isOpen) {
if (System.currentTimeMillis() - lastFailureTime > retryDelayMs) {
// 半开状态,允许一次试探
System.out.println("Circuit breaker half-open, allowing probe request");
isOpen = false;
} else {
throw new CircuitBreakerOpenException("Circuit breaker is open");
}
}
try {
T result = callable.call();
onSuccess();
return result;
} catch (Exception e) {
onFailure();
throw e;
}
}
private synchronized void onSuccess() {
failureCount = 0;
isOpen = false;
}
private synchronized void onFailure() {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= failureThreshold) {
isOpen = true;
System.out.println("Circuit breaker opened after " + failureCount + " failures");
}
}
}
class CircuitBreakerOpenException extends RuntimeException {
public CircuitBreakerOpenException(String message) {
super(message);
}
}
// ==================== 场景三:安全性 - 防重复提交 ====================
/**
* 防重复提交拦截器
*
* 场景:防止用户重复提交同一订单
* 策略:使用唯一令牌机制
*/
class IdempotencyInterceptor {
private final Map<String, Long> processedRequests;
private static final long TTL_MS = 3600000; // 1 小时过期
public IdempotencyInterceptor() {
this.processedRequests = new ConcurrentHashMap<>();
}
/**
* 检查请求是否已处理
* @return true 如果是新请求,false 如果是重复请求
*/
public boolean isNewRequest(String idempotencyKey) {
long now = System.currentTimeMillis();
// 清理过期记录
processedRequests.entrySet().removeIf(entry -> now - entry.getValue() > TTL_MS);
// 检查是否已处理
Long previousTime = processedRequests.putIfAbsent(idempotencyKey, now);
if (previousTime != null) {
System.out.println("Duplicate request detected for key: " + idempotencyKey);
return false;
}
return true;
}
}
// ==================== 辅助接口和类 ====================
interface OrderRepository {
void save(Order order);
void update(Order order);
Order findById(String orderId);
}
interface InventoryService {
boolean reserveStock(List<InventoryReservation> reservations);
void releaseStock(List<InventoryReservation> reservations);
}
interface PaymentService {
boolean processPayment(String orderId, double amount);
}
class InventoryReservation {
private final String productId;
private final int quantity;
public InventoryReservation(String productId, int quantity) {
this.productId = productId;
this.quantity = quantity;
}
public String getProductId() { return productId; }
public int getQuantity() { return quantity; }
}
// ==================== 测试 ====================
public class ScenarioDrivenDesign {
public static void main(String[] args) throws Exception {
System.out.println("===== 场景驱动设计演示 =====\n");
// 模拟依赖
OrderRepository orderRepo = new InMemoryOrderRepository();
InventoryService inventoryService = new MockInventoryService();
PaymentService paymentService = new MockPaymentService();
// 创建异步处理器
AsyncOrderProcessor processor = new AsyncOrderProcessor(
orderRepo, inventoryService, paymentService
);
// 场景一:测试快速创建订单
System.out.println("--- 场景一:高峰期订单提交 ---");
List<OrderItem> items = Arrays.asList(
new OrderItem("PROD-1", 2, 99.9),
new OrderItem("PROD-2", 1, 199.9)
);
long startTime = System.currentTimeMillis();
Order order = processor.createOrder("USER-123", items).get();
long elapsed = System.currentTimeMillis() - startTime;
System.out.println("订单创建耗时: " + elapsed + "ms");
System.out.println("订单 ID: " + order.getOrderId());
System.out.println("订单状态: " + order.getStatus());
System.out.println("(注意:订单已创建,但支付和库存扣减在后台异步进行)\n");
// 等待异步处理完成
Thread.sleep(2000);
// 场景二:测试支付服务故障
System.out.println("--- 场景二:支付服务故障 ---");
PaymentService failingPaymentService = new AlwaysFailingPaymentService();
ResilientPaymentService resilientPaymentService = new ResilientPaymentService(failingPaymentService);
boolean result = resilientPaymentService.processPayment("ORDER-TEST", 100.0);
System.out.println("支付结果: " + result);
System.out.println("(注意:支付失败但订单未失败,标记为待支付)\n");
// 场景三:测试防重复提交
System.out.println("--- 场景三:防重复提交 ---");
IdempotencyInterceptor interceptor = new IdempotencyInterceptor();
String idempotencyKey = "ORDER-SUBMIT-123";
boolean first = interceptor.isNewRequest(idempotencyKey);
boolean second = interceptor.isNewRequest(idempotencyKey);
System.out.println("第一次提交: " + (first ? "允许" : "拒绝"));
System.out.println("第二次提交: " + (second ? "允许" : "拒绝"));
processor.shutdown();
}
}
// ==================== 模拟实现 ====================
class InMemoryOrderRepository implements OrderRepository {
private final Map<String, Order> orders = new ConcurrentHashMap<>();
@Override
public void save(Order order) {
orders.put(order.getOrderId(), order);
System.out.println("Order saved: " + order.getOrderId());
}
@Override
public void update(Order order) {
orders.put(order.getOrderId(), order);
System.out.println("Order updated: " + order.getOrderId() + ", status: " + order.getStatus());
}
@Override
public Order findById(String orderId) {
return orders.get(orderId);
}
}
class MockInventoryService implements InventoryService {
@Override
public boolean reserveStock(List<InventoryReservation> reservations) {
System.out.println("Reserving stock for " + reservations.size() + " items");
return true; // 模拟成功
}
@Override
public void releaseStock(List<InventoryReservation> reservations) {
System.out.println("Releasing stock for " + reservations.size() + " items");
}
}
class MockPaymentService implements PaymentService {
@Override
public boolean processPayment(String orderId, double amount) {
System.out.println("Processing payment for order " + orderId + ": $" + amount);
return true; // 模拟成功
}
}
class AlwaysFailingPaymentService implements PaymentService {
@Override
public boolean processPayment(String orderId, double amount) {
System.out.println("Payment service failed for order " + orderId);
throw new RuntimeException("Payment service unavailable");
}
}这个示例展示了如何根据三个质量属性场景设计系统:
性能场景:使用异步处理,订单创建立即返回
可用性场景:使用熔断器,支付服务故障时不导致订单失败
安全性场景:使用幂等性令牌,防止重复提交
4.2 示例二:场景验证框架
下面我们用 Python 实现一个简单的场景验证框架。
"""
质量属性场景验证框架
用于定义、执行和验证质量属性场景
"""
from dataclasses import dataclass, field
from typing import Callable, List, Optional, Any
from enum import Enum
import time
class ScenarioStatus(Enum):
"""场景执行状态"""
PENDING = "pending" # 待执行
RUNNING = "running" # 执行中
PASSED = "passed" # 通过
FAILED = "failed" # 失败
@dataclass
class QualityAttributeScenario:
"""质量属性场景定义"""
name: str
quality_attribute: str # 性能、可用性、安全性等
# 六要素
source: str # 刺激源
stimulus: str # 刺激
environment: str # 环境
artifact: str # 制品
response: str # 响应
response_measure: str # 响应度量
# 验证相关
test_function: Optional[Callable] = None
status: ScenarioStatus = ScenarioStatus.PENDING
actual_result: Optional[Any] = None
expected_result: Optional[Any] = None
execution_time: float = 0.0
def __str__(self):
return f"""
【{self.quality_attribute}】{self.name}
刺激源: {self.source}
刺激: {self.stimulus}
环境: {self.environment}
制品: {self.artifact}
响应: {self.response}
响应度量: {self.response_measure}
状态: {self.status.value}
"""
class ScenarioValidator:
"""场景验证器"""
def __init__(self):
self.scenarios: List[QualityAttributeScenario] = []
def add_scenario(self, scenario: QualityAttributeScenario):
"""添加场景"""
self.scenarios.append(scenario)
def execute_scenario(self, scenario: QualityAttributeScenario) -> bool:
"""执行单个场景"""
print(f"\n执行场景: {scenario.name}")
print("-" * 50)
scenario.status = ScenarioStatus.RUNNING
start_time = time.time()
try:
if scenario.test_function:
result = scenario.test_function()
scenario.actual_result = result
# 验证结果
if scenario.expected_result is not None:
passed = result == scenario.expected_result
else:
# 如果没有预期结果,假设返回 True 表示通过
passed = result is True or result is None
scenario.status = ScenarioStatus.PASSED if passed else ScenarioStatus.FAILED
else:
# 没有测试函数,标记为待实现
scenario.status = ScenarioStatus.FAILED
raise NotImplementedError("Test function not implemented")
except Exception as e:
scenario.status = ScenarioStatus.FAILED
scenario.actual_result = str(e)
print(f"场景执行失败: {e}")
finally:
scenario.execution_time = time.time() - start_time
print(f"执行时间: {scenario.execution_time:.3f}s")
print(f"状态: {scenario.status.value}")
return scenario.status == ScenarioStatus.PASSED
def execute_all(self) -> dict:
"""执行所有场景"""
results = {
"total": len(self.scenarios),
"passed": 0,
"failed": 0,
"details": []
}
for scenario in self.scenarios:
passed = self.execute_scenario(scenario)
if passed:
results["passed"] += 1
else:
results["failed"] += 1
results["details"].append({
"name": scenario.name,
"status": scenario.status.value,
"execution_time": scenario.execution_time
})
return results
def report(self) -> str:
"""生成报告"""
lines = [
"\n" + "=" * 60,
"质量属性场景验证报告",
"=" * 60,
]
for scenario in self.scenarios:
lines.append(str(scenario))
results = {s.name: s.status.value for s in self.scenarios}
passed = sum(1 for s in self.scenarios if s.status == ScenarioStatus.PASSED)
lines.extend([
"-" * 60,
f"总计: {len(self.scenarios)} 个场景",
f"通过: {passed}",
f"失败: {len(self.scenarios) - passed}",
f"通过率: {passed / len(self.scenarios) * 100:.1f}%",
"=" * 60,
])
return "\n".join(lines)
# ==================== 示例场景 ====================
def test_response_time():
"""测试响应时间场景"""
start = time.time()
# 模拟处理
time.sleep(0.1)
elapsed = time.time() - start
# 要求响应时间 < 0.5 秒
return elapsed < 0.5
def test_fault_tolerance():
"""测试容错场景"""
try:
# 模拟依赖服务调用
raise ConnectionError("Service unavailable")
except ConnectionError:
# 系统应该捕获异常并降级
return True # 降级成功
return False
def test_idempotency():
"""测试幂等性场景"""
processed = set()
# 第一次提交
key = "request-123"
if key in processed:
return False # 不应该被识别为重复
processed.add(key)
# 第二次提交
if key in processed:
return True # 正确识别为重复
return False
if __name__ == "__main__":
validator = ScenarioValidator()
# 添加性能场景
perf_scenario = QualityAttributeScenario(
name="高峰期订单提交",
quality_attribute="性能",
source="终端用户",
stimulus="提交订单请求",
environment="促销活动期间,系统负载为正常水平的 5 倍",
artifact="订单创建服务",
response="系统接受订单请求,立即返回确认",
response_measure="P95 响应时间 < 2 秒",
test_function=test_response_time,
expected_result=True
)
validator.add_scenario(perf_scenario)
# 添加可用性场景
avail_scenario = QualityAttributeScenario(
name="支付服务故障",
quality_attribute="可用性",
source="第三方支付网关",
stimulus="支付服务返回超时错误",
environment="正常运营时段",
artifact="订单支付模块",
response="系统降级处理,订单标记为待支付",
response_measure="订单创建成功率 > 99%",
test_function=test_fault_tolerance,
expected_result=True
)
validator.add_scenario(avail_scenario)
# 添加安全性场景
security_scenario = QualityAttributeScenario(
name="防止重复提交",
quality_attribute="安全性",
source="终端用户",
stimulus="短时间内多次点击提交按钮",
environment="网络延迟较高",
artifact="订单提交接口",
response="系统识别并拒绝重复请求",
response_measure="重复请求 100% 被阻止",
test_function=test_idempotency,
expected_result=True
)
validator.add_scenario(security_scenario)
# 执行所有场景
results = validator.execute_all()
# 生成报告
print(validator.report())
print("\n详细结果:")
for detail in results["details"]:
print(f" {detail['name']}: {detail['status']} ({detail['execution_time']:.3f}s)")这个框架提供了:
场景定义:结构化的场景表示,包含六要素
场景执行:自动执行测试函数并记录结果
结果验证:比较实际结果与预期结果
报告生成:生成详细的验证报告
五、深入思考
5.1 历史背景
质量属性场景的概念起源于 20 世纪 90 年代的软件架构研究。
起源
- 1996 年:Len Bass、Paul Clements 和 Rick Kazman 在《Software Architecture in Practice》第一版中首次系统性地提出了质量属性场景的概念
- 1998 年:他们在 SEI(软件工程研究所)开发了 ATAM(架构权衡分析方法),场景成为该方法的核心组成部分
发展
- 2000s:场景方法被广泛应用于工业界,成为架构评估的标准实践
- 2005 年:ISO/IEC 25010 质量标准发布,吸收了场景思想
- 2010s:敏捷开发兴起,场景方法与用户故事结合,形成"质量故事"实践
- 2015 年:DevOps 普及,场景扩展到部署和运维领域
影响
质量属性场景的影响超越了软件架构领域:
- 系统工程:被应用于硬件和嵌入式系统
- 产品管理:用于定义非功能性需求
- 测试工程:指导性能和安全性测试设计
- 项目管理:作为风险评估和优先级排序的工具
关键人物
- Len Bass, Paul Clements, Rick Kazman:场景方法的创始人
- Mary Shaw:软件架构教育的先驱,推广了场景在教学中的应用
- Grady Booch:统一建模语言(UML)的创造者之一,将场景纳入建模方法
5.2 未解决的问题
场景的形式化验证
目前,场景的验证主要依靠测试和经验。能否开发出形式化方法,数学地证明架构满足特定场景的要求?这对于安全关键系统尤为重要。
场景的自动生成
能否从业务需求或用户故事中自动推导出质量属性场景?这需要自然语言处理和领域知识的结合。
场景的动态更新
当前的场景是静态定义的。在运行时,系统能否根据实际表现动态调整场景的优先级或度量标准?
场景与 AI 的结合
AI 系统(如机器学习模型)的质量属性(如公平性、可解释性)如何用场景描述?传统的场景方法是否需要扩展?
大规模场景管理
在大型系统中,场景数量可能达到数百甚至数千。如何有效地组织、检索和维护这些场景?
5.3 延伸阅读
《Software Architecture in Practice》(第 4 版) - Len Bass, Paul Clements, Rick Kazman
本书对质量属性场景有最权威的讲解,包括场景的构造方法和在 ATAM 中的应用。
《Evaluating Software Architectures: Methods and Tools》 - Paul Clements, Rick Kazman, Linda Northrop
详细介绍 ATAM 方法,场景是该方法的核心组成部分。
《Writing Effective Use Cases》 - Alistair Cockburn
虽然主要讲用例,但其中的场景写作技巧对质量属性场景也有借鉴意义。
"Quality Attribute Scenarios: A Method for Specifying and Analyzing Quality Attributes"(论文) - Len Bass et al.
场景方法的原始论文,详细介绍了理论基础和实践指南。
《Agile Modeling》 - Scott Ambler
介绍了如何在敏捷环境中使用轻量级场景方法。
六、实验笔记
实验一:为真实系统编写质量属性场景
目标:练习将模糊的质量需求转化为具体的质量属性场景。
步骤:
选择一个你熟悉的系统(可以是开源项目、公司内部系统或个人项目)
采访至少 2 位利益相关者(用户、产品经理、开发人员等),收集他们对系统质量的期望
将收集到的模糊需求整理为初步的质量属性列表
为每个关键质量属性编写 3-5 个质量属性场景
确保每个场景都包含完整的六要素
与利益相关者评审场景,确认准确性和完整性
根据反馈修改场景
预期结果:
- 一份利益相关者访谈记录
- 至少 15 个完整的质量属性场景(覆盖至少 5 个质量属性)
- 场景评审会议纪要
- 修订后的场景文档
场景质量检查清单:
- [ ] 六要素是否齐全?
- [ ] 刺激源是否明确?
- [ ] 刺激是否具体?
- [ ] 环境条件是否清晰?
- [ ] 制品是否准确定位?
- [ ] 响应是否是具体行动?
- [ ] 响应度量是否可验证?
- [ ] 场景是否现实可行?
思考题:
在访谈过程中,利益相关者最难表达的是哪类质量需求?为什么?
哪些场景在评审时被修改最多?反映了什么问题?
场景编写过程中,你发现了哪些之前未被注意到的质量需求?
实验二:场景驱动的架构评估
目标:使用质量属性场景评估现有架构。
步骤:
获取一个系统的架构文档(或自行绘制)
准备一组质量属性场景(可以使用实验一的结果)
对每个场景,分析:
a. 当前架构是否能满足该场景?
b. 如果能,是通过什么机制实现的?
c. 如果不能,缺口在哪里?
d. 需要什么样的架构改进?
识别架构的风险点和敏感点
提出架构改进建议
估算改进所需的工作量
评估模板:
场景:[场景名称]
架构分析:
- 相关组件:[列出受影响的组件]
- 当前机制:[描述当前如何处理此类场景]
- 满足程度:[完全满足 / 部分满足 / 不满足]
风险评估:
- 风险等级:[高 / 中 / 低]
- 风险描述:[详细说明]
改进建议:
- 建议方案:[描述改进措施]
- 预期效果:[改进后的表现]
- 工作量估算:[人天]预期结果:
- 一份完整的架构评估报告
- 至少 10 个场景的详细分析
- 架构风险清单
- 改进建议和优先级排序
思考题:
哪些场景最容易暴露出架构弱点?
架构改进建议中,哪些是短期可以做到的,哪些需要长期规划?
如果资源有限,只能实施一项改进,你会选择哪一项?为什么?
七、本章小结
质量属性场景是将模糊的质量需求转化为具体、可验证要求的结构化方法。
核心要点:
场景六要素:
- 刺激源(Source):谁或什么产生了刺激
- 刺激(Stimulus):发生了什么事件
- 环境(Environment):在什么条件下发生
- 制品(Artifact):系统的哪些部分受到影响
- 响应(Response):系统如何应对
- 响应度量(Response Measure):如何衡量响应的有效性
场景的价值:
- 精确描述质量需求,消除歧义
- 驱动架构设计,使决策有据可依
- 作为评估标准,检验架构是否满足要求
- 促进沟通,在利益相关者之间建立共同理解
场景构造方法:
- 识别关键质量属性
- 头脑风暴可能的刺激
- 定义环境和制品
- 描述响应和度量
- 审查和完善
场景的分类:
- 按质量属性:性能、可用性、安全性等
- 按刺激类型:正常、异常、恶意
- 按粒度:系统级、子系统级、组件级
- 按优先级:关键、重要、期望
场景的演化:
- 场景不是一次性产物,需要持续更新
- 场景应与架构决策建立关联
- 场景可以转化为测试用例
场景与普通需求的对比:
| 维度 | 普通需求 | 质量属性场景 |
|---|---|---|
| 精确度 | 模糊 | 精确 |
| 可验证性 | 难以验证 | 可测量 |
| 完整性 | 常缺少上下文 | 六要素齐全 |
| 沟通效果 | 易产生歧义 | 清晰明确 |
| 驱动设计 | 间接 | 直接 |
场景的最佳实践:
- 聚焦关键质量属性,不要试图覆盖所有
- 每个属性 3-5 个代表性场景
- 场景应由多方利益相关者共同参与
- 场景应随项目进展持续更新
- 场景应与测试和验收关联
八、习题
基础题
列出质量属性场景的六个要素,并简要说明每个要素的作用。
将以下模糊需求转化为质量属性场景:
a) "系统要快"
b) "系统要稳定"
c) "系统要安全"
解释为什么"系统在正常情况下响应时间应小于 2 秒"不是一个完整的质量属性场景。缺少的要素是什么?
区分以下刺激类型:正常刺激、异常刺激、恶意刺激,并各举一个例子。
什么是响应度量?为什么它是场景中最重要的要素之一?
进阶题
为一个在线银行系统设计质量属性场景,覆盖以下属性:
a) 性能
b) 安全性
c) 可用性
每个属性至少 2 个场景,确保六要素完整。
分析以下场景的问题,并提出改进建议:
```
【性能】用户登录
刺激源:用户
刺激:登录
环境:正常
制品:登录模块
响应:快速响应
响应度量:快
```
假设你要评估一个微服务架构的系统,请设计一套场景来评估其可修改性。至少包含 5 个场景,涵盖代码修改、配置变更、服务替换等方面。
挑战题
设计一个场景优先级评估模型:
a) 定义评估维度(如业务价值、风险程度、实现难度、发生频率)
b) 为每个维度设计评分标准
c) 用一个具体案例演示如何使用该模型对 10 个场景进行排序
d) 分析排序结果的合理性
研究 ATAM(架构权衡分析方法)中的场景应用:
a) ATAM 如何使用场景进行架构评估?
b) 什么是效用树(Utility Tree)?它与场景有什么关系?
c) 设计一个简化的 ATAM 评估流程,用场景驱动整个评估过程
d) 用一个开源项目为例,执行这个简化流程并报告结果