053 — 领域组件设计模式:bill_base 与 asset_base 的领域驱动设计
简介
在传统 MVC 架构中,业务逻辑和 UI 代码往往混杂在一起,导致代码难以复用和测试。MoneyTrack 引入了领域驱动设计(DDD)思想,将核心业务逻辑抽取到领域基础包bill_base和asset_base中。这些基础包遵循"无 UI 纯逻辑"的原则,只包含 Enums(枚举)、Types(类型定义)和 Models(数据模型)三部分,不依赖任何 UI 框架。这种设计让同一套业务逻辑可以被不同 UI 组件复用,也大幅简化了单元测试——测试领域包不需要搭建 UI 环境。
核心知识点
1. DDD 领域驱动设计
DDD 将应用按业务领域拆分为独立的"限界上下文":
- bill_base:账单位领域,包含账单类型枚举、账单状态、收支类别、账单查询参数。
- asset_base:资产领域,包含资产类型、资产账户模型、货币类型、资产统计聚合。
- 每个领域包自治管理自己的数据结构和业务规则,彼此之间通过 common 层的共享基类(如
BaseEntity)实现低耦合的跨领域引用。
2. 领域包的三层结构
每一个领域基础包都严格遵循 Enums / Types / Models 三层架构:
Enums 层:定义领域内所有的枚举常量。例如BillType.EXPENSE表示支出、AssetType.CASH表示现金资产。
Types 层:定义 TypeScript 接口和类型别名,用于跨模块的数据传递约定。例如BillQueryParams定义了账单列表的筛选条件接口。
Models 层:定义数据模型类,包含数据验证、格式化、业务计算等纯方法,不涉及任何 UI 渲染。
3. 领域包与 UI 包的分离原则
领域包与 UI 包的分离是 DDD 在鸿蒙工程中落地的核心原则:
- 领域包(bill_base / asset_base):纯 ArkTS 代码,零 UI 依赖,可独立编译和测试。
- UI 包(bill_card / asset_card):包含 @Component 声明,依赖 UI 框架,引用领域包的数据类型。
- 分离的好处:当 UI 框架升级或替换时,领域逻辑不受影响;同一套领域逻辑可以同时服务于多个 UI 组件。
4. 跨领域引用
虽然每个领域包独立自治,但在实际业务中,账单与资产存在自然的关联。跨领域引用通过 common 层的共享基类实现:
// common/lib-utils/src/main/ets/BaseEntity.etsexportabstractclassBaseEntity{id:string;createdAt:string;updatedAt:string;abstractvalidate():boolean;}// bill_base/Models/BillModel.etsimport{BaseEntity}from'@moneytrack/lib-utils';exportclassBillModelextendsBaseEntity{amount:number;categoryId:string;assetId:string;// 关联资产ID,通过 ID 引用而非直接引用 AssetModelvalidate():boolean{returnthis.amount>0&&!!this.categoryId;}}这里的关键原则是"通过 ID 引用而非对象引用"——BillModel 持有 assetId 字段指向资产,但不直接持有 AssetModel 对象,从而避免 bill_base 对 asset_base 的强依赖。
项目代码案例
Enums/Types/Models 三层完整代码框架
Enums/BillType.ets:
exportenumBillType{EXPENSE='expense',INCOME='income',TRANSFER='transfer'}Types/BillQueryParams.ets:
exportinterfaceBillQueryParams{type?:BillType;startDate?:string;endDate?:string;categoryIds?:string[];minAmount?:number;maxAmount?:number;keyword?:string;}Models/BillValidator.ets(业务校验逻辑):
exportclassBillValidator{staticvalidateAmount(amount:number):string|null{if(amount<=0)return'金额必须大于零';if(amount>99999999.99)return'金额超出上限';if(!/^\d+(\.\d{1,2})?$/.test(String(amount)))return'金额格式错误,最多两位小数';returnnull;}staticvalidateBill(bill:BillModel):string[]{consterrors:string[]=[];constamountError=this.validateAmount(bill.amount);if(amountError)errors.push(amountError);if(!bill.categoryId)errors.push('请选择收支类别');if(!bill.assetId)errors.push('请选择关联资产');returnerrors;}}asset_base/Models/AssetModel.ets:
exportclassAssetModel{id:string;name:string;type:AssetType;balance:number;currency:string;formatBalance():string{return`¥${this.balance.toFixed(2)}`;}isOverdrawn():boolean{returnthis.balance<0;}}领域驱动设计的核心价值在于:业务逻辑独立于 UI 框架、可复用、可测试,为后续 6 个 UI 组件包的开发提供了稳固的基础。
推荐参考文档
- 领域驱动设计(DDD)基础
- 组件化架构设计模式
- ArkTS 面向对象编程指南