软件建模06:状态、流程和决策
设备已经被预留,客户还没有来取。柜台需要知道能否继续交付,仓库需要知道接下来由谁处理,报价需要知道八天租期适用哪档单价。这三个问题发生在同一笔租赁中,却不能只靠一个“处理中”字段回答。字段混在一起以后,调价可能改动流程分支,补上超时又可能意外允许已经归还的设备再次交付。 UML 类图与交互图 讨论对象之间的关系与消息。本篇把观察范围缩到一台设备的一轮交付,用状态、流程和决策分别检查允许的变化、协作顺序与计算条件。示例仍是合成教学场景,增加的规则需要单独说明。 设备状态记录什么 系列中的 RentalDesk 已有指定设备预留、重叠拒绝、取消和原回执重放。它没有交付、验收或报价功能。本篇新增独立候选模型:设备可处于可用、待领取、已交付、待验收四种状态;归还后必须验收才能再次交付使用。这里的“必须验收”是教学假设,并非从既有代码或行业惯例推导出的事实。 状态描述当前设备在这个模型里的业务处境。事件表达发生了什么,迁移决定事件能否把设备带到另一个状态。可用设备收到预留事件后进入待领取;待领取设备在截止时刻之前才能交付。归还进入待验收,验收完成后恢复可用。只有待领取状态接受超时释放。 ...
软件建模05:UML 类图与交互图
类图里列出 RentalDesk、ReserveRequest 和 Period,仍无法确认取消以后重试会走哪条分支。时序图若把一条 refund() 消息发给根本没有这个操作的回执对象,看起来同样流畅。两张图分别画完还不够,至少要核对消息接收者是否存在、它声明了什么操作,以及取得它的引用是否符合结构关系。 OO 分析与 CRC 比较了集中柜台和职责拆分两种候选。这里选择仍在累计代码中的 `RentalDesk`,用“取消已确认请求,再重放原请求”这个用例连接类图与交互图。图只覆盖实际实现的局部,避免把候选类误画成已经存在的代码。 先确定图描述哪个系统 OMG UML 2.5.1 §11.4 描述类及其结构和行为特征,§11.5 描述实例之间的关联;§17 则讨论交互、生命线和消息。本文使用这些概念组织模型,再用 Mermaid 绘制一个受限记法子集。Mermaid 文件通过解析和检查,不表示通过了完整 UML 规范一致性认证。UML 2.5.1 正式规范 图中 RentalDesk 对应真实 Java 类。Receipt 是它的私有嵌套 record,展示它是为了说明内部协作...
软件建模04:OO 分析与 CRC
“设备、租赁、请求、回执”能列成一串名词,却不能说明取消时该删除什么。设备仍然存在,原请求也确实发生过;需要移除的是当前占用。如果仅给这些名词各建一个类,再把全部判断交给调用方,类的数量增加了,取消后重试是否重新占位仍没有明确的负责人。 ER 与数据建模 已经区分回执与有效预留的关系。OO 分析继续追问:谁掌握判断所需的信息,谁完成检查,谁可以改变占用,其他对象通过什么消息获得结果。CRC 卡可以把这些决定写得足够短,再用场景检查遗漏。 从要完成的动作寻找职责 CRC 通常按类名、职责和协作者组织一张卡。Beck 与 Cunningham 的 1989 年论文 §2 要求职责用带主动动词的短语表达,并把发送或接收相关消息的对象列为协作者。这样的记录比属性清单更接近一次实际协作:写“比较重复请求的载荷”,就必须说明需要哪两个请求以及比较结果怎样影响后续动作。原论文 §2 “保存设备编号”只能说明对象含有什么;“拒绝同设备重叠预留”同时暴露了行为和信息需求。作出后一个决定需要设备标识、候选租期以及已有有效预留。一个只保存设备名称的 Equipment 对象没有这些信息,不能因为业务...
软件建模03:ER 与数据建模
一台设备可以经历很多次租赁请求,其中有些被拒绝,有些曾经确认但后来取消。若只保存“设备当前属于谁”,历史请求就无法重放;若把全部确认回执都当作当前占用,取消又没有真正释放设备。建立 ER 模型时,实体和关系的选择已经影响这两种行为,远早于字段选用字符串还是整数。 结构化分析与数据流 区分了回执与有效预留的数据去向。关系模型需要进一步回答:它们各自靠什么识别,一条记录能关联多少设备,哪些关联允许暂时缺失,取消以后哪些关系仍然存在。SQL 实验可以检验其中一部分,不能替代全部租赁规则。 身份来自需要区分的事实 系列基线接受指定设备、租期和请求标识,不包含完整合同。设备 E1 与 E2 即使型号相同,仍是两台不同实物;请求 A 与 B 即使设备和租期相同,仍可能代表两次不同尝试。不能用“设备加开始时刻”直接替代请求身份,否则独立尝试与同一次请求重试就被合并了。 回执以请求标识识别,因为当前契约要求同一请求 ID 重放最初结果。有效预留也使用该标识连接回执:基线一次请求只指定一台设备和一个租期,因此每份回执至多对应一条有效预留。这个选择依赖当前约束;如果一个请求能够预留三台设备,子项身...
软件建模02:结构化分析与数据流
同一个预留请求被取消以后,系统仍能返回它最初的处理结果。这条约定要求保留两份用途不同的数据:当前占用设备的预留,以及记住原请求结果的回执。只画“客户提交、系统处理、返回结果”,看不出取消究竟删掉哪一份数据;直接画数据库表,又容易跳过这些数据从哪里来、由谁产生的问题。 数据流图适合检查这种信息关系。它把系统视为接收数据、转换数据和产生数据的过程,再逐层展开过程内部的组成。画图的目标是发现遗漏的输入、来源不明的输出和说不清含义的数据名。图本身不负责证明程序执行顺序,也不能证明两次并发预留不会同时成功。 本篇承接 场景、用例与规则,继续使用单实例、单线程的设备租赁教学案例。客户、合同和结算属于系列背景,当前边界只覆盖指定设备预留、重复请求与取消。这里没有企业访谈,也没有从现实业务推导出一套未经确认的流程。 先固定系统边界 外部角色取“租赁经办角色”,表示提交请求的调用方。它可以由网页、命令行或另一段程序承担,图里不决定界面形态。设备不是外部参与者:设备标识是请求中的数据,设备本身不会调用这个基线。维修平台尚未接入,因此也不为了让图看起来完整而添加一条维修通知。 上下文图把整个租赁预留...
软件建模01:场景、用例与规则
同一台设备不能在重叠的租期里预留给两个请求。这个要求很短,却没有回答:12 点归还的设备能否从 12 点再次出租?取消后重复提交旧请求,会不会重新占用设备?请求被拒绝后,资源空闲了,重试是否应该成功?这些分支会改变实现,即使页面上始终只有一个“预留”按钮。 上一篇:模型帮助作出什么决定 已建立指定设备预留的 Java 21 内存基线。这里沿用其合成教学材料,提取一份能定位来源、找到反例并对照代码的用例。所有业务约定都来自教学案例,没有真实访谈、企业制度或生产验证作为背书。 目标决定用例的范围 租赁经办角色希望取得设备 E1 在指定时段的预留承诺。“填写表单”和“发送请求”是完成目标的步骤;只证明按钮能点击,无法证明预留成立。成功后应有一条有效预留,后来与它冲突的新请求应被拒绝。这样,目标就有了可观察的后果。 Jacobson 与 Cockburn 的《Use-Case Foundation》v1.1 把用例组织为某个主要参与者为达成目标而使用系统的一组场景,包含成功、遇到障碍和失败的路径。第 4 页进一步区分基本场景与扩展路径;用例的精度取决于这些路径是否准确,而不取决于步骤写...
软件建模00:模型帮助作出什么决定
软件建模00:模型帮助作出什么决定 设备租赁系统收到两笔订单:一笔租相机 E1,从 10 点到 12 点;另一笔也租 E1,从 11 点到 13 点。登记两条记录很容易,决定能否接受第二笔才是业务问题。若第二笔从 12 点开始,还要确定上一笔的结束时刻是否占用资源,以及设备归还后是否需要清洁。 这些问题会改变字段、流程和代码。模型的用途,就是把影响决定的事实和约束表达出来,使场景能够被检查。一个模型可以很小:一张表、一段流程、一组概念关系,或者一段可执行规则。维护哪些表达,取决于哪些决定需要作出。 本系列使用合成的设备租赁案例,把分析模式、四色建模、FDD、DDD 和模型驱动开发放进同一条问题线。它们各自处理的问题、产物与验证方式会保留,比较时使用相同场景和变化卡。领域规则复杂到需要边界、存储和集成机制时,再连接独立的企业应用架构系列。 当前问题 需要留下的表达 检查方式 同一设备能否接受两笔预留 租期、设备标识、重叠约束 相邻与重叠区间反例 同一个请求再次到达怎么办 请求标识、载荷、处理回执 重试与新请求对照 取消后还保留哪些事实 当前预留和原请求结果的区...
JPA 持久化 02:脏检查、flush 与回滚的三道边界
发出了 UPDATE,申请为什么还是 DRAFT 采购申请在事务内从 DRAFT 改为 SUBMITTED,执行 flush() 后,数据库可能已经收到 UPDATE;接着审批前置检查失败,事务回滚,另一个上下文重读仍是 DRAFT。若把“SQL 已经执行”“同一上下文读到 SUBMITTED”和“数据库已提交”合成一个事实,就会误报审批进度。另一个方向也成立:在默认 AUTO flush mode 下,同一事务内的 JPQL 查询不能忽略会影响结果的托管状态修改;但由此推断“查询之前必然执行一条固定 SQL”仍是不成立的。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 1b08ada,SHA-256 见源码清单。 本章沿用真正的 examples/jpa/ 工程,使用 jpa_lab 独立 PostgreSQL 16 库,Java SE RESOURCE_LOCAL,而非另设教学数据库。00 讲启动,01 讲身份;这里固定同一申请的一次修改来拆开内存、数据库事务与提交后的行。实体的 submit() 在有明细且处于 DRAFT 时才合法,ORDERED 只能经后续审...
深入 Play E08:国际化、内容协商与缓存隔离
同一个文档路由可以返回法文 JSON,也可以返回英文 HTML。这些响应拥有同一个资源入口,却不能共用一个只包含路由名的缓存键。本篇的错误缓存实验中,Bob 已通过自己的租户检查,仍然读到了 Alice 的英文结果;响应甚至带着 private, no-store,因为泄漏发生在应用内部缓存。 语言、媒体类型、身份和输出转义分别决定响应的不同部分。Play 提供语言选择与模板基础设施,应用仍要定义可接受的表示、缓存分区和授权顺序。 固定版本与证据范围 下载国际化与协商工程及实验记录,核对SHA256SUMS。解压后的 play-electives/e08-i18n 是独立应用;接入后重跑2项JUnit、stage与36次HTTP观察,16个应用及生成class与生产jar字节一致,进程退出143、缓存清空且端口关闭,见 evidence/e08/integration。 独立工程为 play-electives/e08-i18n,固定 Play 3.0.6、Scala 2.13.15、sbt 1.10.7 与 JDK 21。源码依据 Play 提交 2e56aff7d4e7a7...
深入 Play E07:虚拟线程与阻塞 IO,等待发生在哪里
两个数据库连接、四个准入名额、每条 SQL 休眠 50 ms。平台线程和虚拟线程在高并发的三轮实验中都接受了四个请求、拒绝了二十个请求。平台模式主要在执行器队列等待,虚拟模式主要在连接池等待;两者成功响应的 p95 都在约 110 ms 附近。 去掉准入后,同一个虚拟线程执行器同时启动 48 个任务。数据库仍只有两个活跃连接,另外 46 个任务等待借连接,最长借用耗时达到 1282 ms。线程创建成本降低之后,下游容量与等待队列仍需要独立限制。 固定版本与实验边界 独立工程位于 play-electives/e07-virtual-threads,没有替换累计工程的 dispatcher,也没有改变 Play 的 HTTP 服务端。控制器将 JDBC 工作交给一个平台线程执行器或一个虚拟线程执行器,并向 Play 返回 CompletionStage<Result>。这个结构延续了第 13 篇的线程隔离、第 22 篇的连接池以及第 31 篇的容量观测。 固定版本为 Play 3.0.6、Scala 2.13.15、sbt 1.10.7、Amazon Corretto...








