企业应用架构08:聚合映射、继承与Lazy Load
加载合同列表时,页面需要金额,却不一定需要每行费用明细。把明细延迟加载可以减少一次未使用的数据读取,也可能在遍历时变成一百次查询。企业应用架构07:Identity Map与Unit of Work 已把合同和明细放进同一提交范围,本篇继续检查映射、加载时机与实际 SQL 数量。 对象结构不必逐个对应表 Money 由金额和币种组成,Period 由开始与结束时间组成,它们已经作为合同表里的多列保存。这符合 Embedded Value 的处理方式:值对象仍保留在代码中,数据库不必为了每一个小对象创建独立主键和独立表。 合同明细需要多行,本例使用 contract_line 子表,由合同映射过程一起加载和保存,接近 Dependent Mapping。明细没有独立业务仓储,身份只在合同内通过行号表达。这个选择取决于当前生命周期,不能由“一对多”自动推导任何子对象都属于同一聚合。 Complete 表示已加载完成的合同与明细,构造时检查明细非空,明细金额之和等于合同总额。列表复制为不可变值。这个检查补充了数据库约束:外键只保证明细引用存在的合同,单行非负 CHECK 不会自动计算...
企业应用架构07:Identity Map与Unit of Work
同一个用例先通过合同查询拿到对象,又通过其他业务路径拿到相同主键的对象,两份对象可能各自修改。数据库只有一行,内存里却有两份状态,后保存的旧值可能覆盖先保存的新值。企业应用架构06:Repository、Gateway与查询对象 解决了查询表达,本篇把对象身份与一组数据库写入放到明确的工作单元内。 实例身份与数据库身份是两件事 Identity Map 记录一个业务事务中已经加载的对象,使相同身份再次出现时返回已有实例。Fowler 的目录条目强调避免同一对象加载两次并被不一致地更新。它首先约束当前工作范围中的对象实例,不能由此推导跨线程、跨进程只有一个对象。 Unit of Work 记录业务事务中需要写回的变化,并协调写入。两者经常协作:身份表避免同一工作单元出现多个受管实例,工作单元知道哪些受管实例变了。一个缓存 Map 加一个名字叫 commit 的方法仍不够,提交是否操作同一个数据库事务必须实际检查。 本章 Work 持有一个 JDBC 连接、按租户与合同身份索引的身份表,以及待保存对象集合。Managed 是当前工作单元管理的合同实例。合同头部仍使用上一章的不可变 ...
企业应用架构06:Repository、Gateway与查询对象
查询“某租户在指定时段可租的相机”需要同时理解类别、设备资格、取消历史和半开租期。把它改写成几个通用 findAll 调用,很容易漏掉条件。企业应用架构05:Active Record与Data Mapper 已完成合同落库,本篇在真实数据集上区分业务集合查询、外部资源访问和查询条件表达。 三个名字对应不同职责 Fowler 的 Repository 在领域与数据映射之间提供接近集合的访问方式,调用者按领域需要寻找对象。Gateway 封装外部资源的访问接口,让其调用方式适合本地代码。Query Object 则用对象结构表达查询,并把结构解释成数据库查询。它们可以协作,不能靠统一加一个 Repository 后缀消除区别。 本例 EquipmentRepository 提供 available,返回满足规则的设备身份集合。AvailabilityQuery 固定表达租户、类别、租期和排序,再生成参数化 SQL。InventoryGateway 读取另一份实际 H2 数据库中的仓库库存。前两者处理租赁数据,后者封装独立资源的连接和查询方式。 flowchart LR U[租...
企业应用架构05:Active Record与Data Mapper
合同对象里有设备身份、租期、金额和取消行为,关系数据库里则有主键、外键、列类型与事务。把对象放进 Map,只能检查对象如何复制;只有真实执行 SQL,再换连接读回,才能发现列精度、关联约束和提交边界造成的差异。企业应用架构04:模块化单体与上下文边界 已限制模块入口,本篇比较两种保存职责的安排。 同一张表,两种保存入口 Fowler 的 Active Record 条目把数据访问和领域行为放在代表数据库行的对象中。Data Mapper 则单独设置对象与表之间的转换层,让领域对象不需要知道表结构。两种方式可以处理相同数据,差异首先在职责依赖,不在是否执行了某一种 SQL。 本例使用受限的 ActiveContract:对象持有合同数据,提供取消、插入和保存状态的方法。它知道数据库操作;取消仍复用相同的合同规则。Data Mapper 路径的 Contract 只有数据和取消行为,ContractMapper 负责插入、读取和更新。两条路径访问同一个 rental_contract 表,使用相同设备和租期输入。 flowchart LR A[ActiveContract:行为...
企业应用架构04:模块化单体与上下文边界
维修检查通过后,租赁可以解除维修限制;是否接受一段新租期,仍由租赁规则决定。如果维修模块能直接拿到预约日历并删除记录,接口上的职责划分就没有约束力。企业应用架构03:分层与端口适配器 检查了内外依赖,本篇把同一进程中的维修、租赁与结算视图拆成可检查的模块。 模块边界要回答数据由谁修改 模块化单体仍然是一个进程,可以通过普通方法调用协作。它要求模块有明确入口、依赖方向和内部状态所有者。把包名改成 maintenance、rental、settlement,并不能阻止彼此读取内部集合;必须把预期边界变成编译或者构建能够检查的规则。 Fowler 在 Monolith First 中强调,单体内部仍需要留意 API 和数据存储的模块性。不能据此推导任意单体都能轻易拆分服务。本例也没有独立部署、独立数据库或进程故障隔离,采用模块系统是为了检查同一程序的源码访问关系。 限界上下文与 Java 模块也没有强制的一对一关系。上下文限定模型语言的含义,模块组织可编译的代码。一个上下文可以包含多个技术模块,多个小模块也可能共享一套领域语言。本章把维修与租赁作为语义边界,把结算实现缩小为只读结果视...
企业应用架构03:分层与端口适配器
租赁申请服务已经能协调资格、维修结果、预约日历与报价。如果把维修读取从内存函数换成文件,用例是否还需要修改?如果显示页面增加一个字段,领域代码是否跟着引入页面对象?这两个问题决定依赖边界是否有用。企业应用架构02:Service Layer 与用例边界 确定了服务入口,本篇为其中一个外部依赖提供真实替换,并让越界引用在编译时失败。 四种组织方式分别关注什么 分层架构把不同责任放入不同层。Evans 在《DDD Reference》的 Layered Architecture 条目中要求把领域模型与界面、应用协调和基础设施分开。层次说明责任,但仅有目录名不能阻止领域对象调用数据库工具类,还需要规定允许的引用方向。 Cockburn 的 Hexagonal Architecture 强调应用内部与外部的区别。端口表达一次有业务含义的交互,适配器负责把外部技术转换成应用能使用的形式。同一端口可以有多个适配器;六边形的边数不要求恰好六个。原文也用内存与文件等替代实现说明应用可以脱离最终设备运行。 Palermo 的 Onion Architecture 把领域模型放在中心,内侧声明所需...
企业应用架构02:Service Layer 与用例边界
命令行可以发起预留,自动化测试也可以发起预留。如果两个入口各自检查维修状态、计算金额、保存日历,它们很容易在新增规则时分叉。把相同逻辑复制两遍以后,再给其中一个类取名 Service,并不能形成共同的应用边界。 本篇在 固定的租赁应用 外提供同一个服务入口,由命令行和检查程序分别调用。实验实际注入计算完成但保存尚未发生的异常,再绕过服务直接调用日历,观察哪些规则随之丢失。 服务边界描述一次应用操作 PoEAA 的 Service Layer 从调用方角度定义应用提供的操作,并协调每次操作的响应。公开模式页署名 Randy Stafford;多种界面重复组织同一交互逻辑,是该模式要处理的问题之一。Service Layer 原文摘要 租赁申请不是单独调用一次金额乘法。它需要识别历史请求,取得设备与维修评估,验证时间,创建报价草案,判断占用,再保存结果。输入来自命令行还是测试,不应改变这些步骤的业务含义。 冻结基线中的 RentalApplication 已承担这项协调。本章的 RentalService 是显式门面,复用它的行为,并把回执状态与已存报价组成调用结果;没有重新复制预...
企业应用架构01:三种领域逻辑组织
租赁费用只有“天数乘单价”时,一个过程就能清楚表达。价格在租期中途变化以后,报价与延期两个入口都要理解有效期;如果报价改成分段收费,延期仍按起租日单价算全期,同一份租期就会出现两个答案。 本篇用 Transaction Script、Domain Model、Table Module 分别实现相同计费输入,实际运行旧规则、新规则和漏改入口的反例。固定租赁基线 保持不变;这里的整日分段价格是隔离的新教学规则,没有偷偷替换上游的小时计费。 先把价格变化说清楚 输入是整日的 LocalDate 半开区间。十月一日至五日每日一百元,五日至十日每日一百五十元;结束日不计费。这里没有把日期转换为时区瞬间,也不涉及夏令时、按小时补差或跨币种换汇。 旧规则以起租日费率计算全期:三日至六日共三天,收费三百元。新规则按租期落入的有效段计算,三日和四日各一百元,五日一百五十元,共三百五十元。这是业务结果的改变,不能称为仅调整结构的等价重构。 延期输入由旧租期与新结束日组成,返回重新计算的全期总额。三日至五日延期到七日,新总额为五百元;它不是新增应收三百元,也没有执行预留或实际收费。缩短结束日不属于此...
企业应用架构00:模式地图与租赁基线
两套租赁程序都返回“预留成功”,不表示它们做了同一件事。一套可能保留重复请求回执,另一套可能重新占用;一套把取消解释为释放设备,另一套可能顺便删除报价。比较架构之前,需要先确定这些结果究竟包含哪些业务保证。 本系列从 软件建模27的累计应用 导入固定源码,重跑原有契约,再与一个直接过程比较。基线先回答设备、金额、租期、维修限制和请求历史的含义;后续章节才逐项改变代码组织、持久化和系统集成方式。 模式地图按问题组织 Fowler 的 PoEAA 目录将领域逻辑、数据源访问、对象与关系数据之间的行为、展示和会话等问题分别组织。目录是模式摘要入口,并没有要求每个企业应用把所有模式都装进去。原作者模式目录 租赁程序可以先用一个过程完成规则判断,也可以把行为分到设备、日历和合同对象。把对象保存到数据库,又会产生身份映射、事务和加载问题;把成功结果发给维修系统,则需要面对消息、重试和恢复。它们沿着同一条业务路径出现,却不属于同一种选择。 flowchart LR B[业务语义与场景] --> L[领域逻辑如何组织] L --> U[应用用例边界] U -...
软件建模E11:企业架构中的业务与技术对齐
租赁系统确认了一笔预留,不代表租赁、维修与结算已经形成一致的企业能力。维修状态可能来自人工登记,结算口径可能独立维护,系统名称相同也可能指向不同责任边界。讨论企业架构时,先把当前事实与目标状态分开,才有可能判断迁移缺少什么。 本系列的现状很小:第 38 篇运行一个 JVM,真实回环 HTTP 进入租赁用例,仓储是内存对象,维修查询是固定的进程内实现,结算应用尚不存在。本篇以这份现状设计租赁、维修、结算三个目标应用,建立带类型的关系图和迁移约束。目标部署全部为 NOT_RUN,实验只验证有限模型规则。 方法、语言与分类框架不能混用 TOGAF 第十版提供企业架构方法与相关指导。本篇采用 ADM 的业务、信息系统、技术架构以及机会方案、迁移规划这些阶段概念来整理问题,不把一次教学建模称为完整 ADM 项目。没有组织治理、利益相关方授权和真实迁移活动,就不能声称企业已经完成架构转型。 ArchiMate 用建模语言表达业务、应用和技术等元素及关系。它可以为 ADM 产生的描述提供表达方式,但不会替团队决定业务目标、预算和迁移顺序。本实验固定为三点二版的有限类型子集,只检查选定的应用组...










