HarmonyOS掌上记账APP开发实践第1篇:HarmonyOS 多模块 HAR 架构最佳实践 — MoneyTrack 多模块拆分之道

HarmonyOS 多模块 HAR 架构最佳实践 --- MoneyTrack 多模块拆分之道

概述

在大型鸿蒙应用开发中,随着业务功能的不断迭代,工程规模会迅速膨胀。如果将所有代码堆砌在单一的 entry 模块中,将会面临一系列棘手问题:编译时间随代码量线性增长、模块间耦合严重导致"改一处动全身"、团队协作时频繁出现代码冲突、业务复用困难等。这些问题在项目达到数十万行代码规模后会急剧恶化,严重拖累开发效率。

MoneyTrack 作为一款功能完备的记账应用,涵盖了账单管理、资产管理、数据报表、家庭账本、会员订阅、消息提醒等多个业务领域,代码量庞大且逻辑复杂。为了应对上述挑战,项目采用多模块 HAR(Harmony Archive)架构,将 21 个功能模块按四层架构精心组织,实现了高度的内聚与低耦合。本文深入剖析该项目的模块划分策略、依赖管理方式以及跨模块引用设计,为构建可扩展的鸿蒙应用提供实战参考。

核心知识点

1. 四层分层架构设计

MoneyTrack 将 21 个 HAR 模块划分为四个层次,层与层之间遵循严格的单向依赖关系:
#mermaid-svg-jwa8k6QuR9Dyy9Mt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .error-icon{fill:#552222;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .marker.cross{stroke:#333333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt p{margin:0;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .cluster-label text{fill:#333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .cluster-label span{color:#333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .cluster-label span p{background-color:transparent;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .label text,#mermaid-svg-jwa8k6QuR9Dyy9Mt span{fill:#333;color:#333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .node rect,#mermaid-svg-jwa8k6QuR9Dyy9Mt .node circle,#mermaid-svg-jwa8k6QuR9Dyy9Mt .node ellipse,#mermaid-svg-jwa8k6QuR9Dyy9Mt .node polygon,#mermaid-svg-jwa8k6QuR9Dyy9Mt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .rough-node .label text,#mermaid-svg-jwa8k6QuR9Dyy9Mt .node .label text,#mermaid-svg-jwa8k6QuR9Dyy9Mt .image-shape .label,#mermaid-svg-jwa8k6QuR9Dyy9Mt .icon-shape .label{text-anchor:middle;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .rough-node .label,#mermaid-svg-jwa8k6QuR9Dyy9Mt .node .label,#mermaid-svg-jwa8k6QuR9Dyy9Mt .image-shape .label,#mermaid-svg-jwa8k6QuR9Dyy9Mt .icon-shape .label{text-align:center;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .node.clickable{cursor:pointer;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .arrowheadPath{fill:#333333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jwa8k6QuR9Dyy9Mt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jwa8k6QuR9Dyy9Mt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jwa8k6QuR9Dyy9Mt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .cluster text{fill:#333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .cluster span{color:#333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jwa8k6QuR9Dyy9Mt rect.text{fill:none;stroke-width:0;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .icon-shape,#mermaid-svg-jwa8k6QuR9Dyy9Mt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .icon-shape p,#mermaid-svg-jwa8k6QuR9Dyy9Mt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .icon-shape .label rect,#mermaid-svg-jwa8k6QuR9Dyy9Mt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jwa8k6QuR9Dyy9Mt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jwa8k6QuR9Dyy9Mt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jwa8k6QuR9Dyy9Mt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 通用层 Commons
组件层 Components
特性层 Features
入口层 Entry
products/entry
features/home
features/statistics
features/assets
features/mine
bill_base
bill_card
bill_chart
bill_manage
bill_data_processing
asset_base
asset_card
asset_manage
aggregated_login
app_setting
collect_personal_info
feedback
membership
module_ui_base
commonlib
lib_network

  • 通用层(commons) :包含 commonlib(工具库、路由、EventBus、断点适配)和 lib_network(网络请求封装与 Mock 适配器)。该层不依赖任何业务模块,为全项目提供基础设施能力。
  • 组件层(components) :包含 bill_baseasset_basebill_cardfeedback 等 14 个模块。每个模块封装独立的业务组件或数据模型,可被多个特性层模块复用。例如 bill_base 提供账单相关的枚举、类型定义和数据模型,bill_card 提供账单卡片 UI 组件。
  • 特性层(features) :包含 homestatisticsassetsmine 四个模块,分别对应应用的四个 Tab 页面。每个特性模块按业务领域聚合页面、ViewModel 和组件,是业务逻辑的核心载体。
  • 入口层(entry) :位于 products/entry,作为应用唯一入口,组装各模块的能力,配置 Ability 和元数据。

2. 模块间依赖管理

在根目录 build-profile.json5 中,21 个模块被逐一注册。以下展示完整的模块注册配置:

json5 复制代码
{
  "app": {
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "targetSdkVersion": "6.0.2(22)",
        "compatibleSdkVersion": "5.0.5(17)",
        "runtimeOS": "HarmonyOS",
        "buildOption": {
          "strictMode": {
            "caseSensitiveCheck": true,
            "useNormalizedOHMUrl": true
          }
        }
      }
    ]
  },
  "modules": [
    { "name": "entry", "srcPath": "./products/entry" },
    { "name": "commonlib", "srcPath": "./commons/commonlib" },
    { "name": "lib_network", "srcPath": "./commons/lib_network" },
    { "name": "home", "srcPath": "./features/home" },
    { "name": "statistics", "srcPath": "./features/statistics" },
    { "name": "assets", "srcPath": "./features/assets" },
    { "name": "mine", "srcPath": "./features/mine" },
    { "name": "bill_base", "srcPath": "./components/bill_base" },
    { "name": "bill_card", "srcPath": "./components/bill_card" },
    { "name": "bill_chart", "srcPath": "./components/bill_chart" },
    { "name": "bill_manage", "srcPath": "./components/bill_manage" },
    { "name": "bill_data_processing", "srcPath": "./components/bill_data_processing" },
    { "name": "asset_base", "srcPath": "./components/asset_base" },
    { "name": "asset_card", "srcPath": "./components/asset_card" },
    { "name": "asset_manage", "srcPath": "./components/asset_manage" },
    { "name": "aggregated_login", "srcPath": "./components/aggregated_login" },
    { "name": "app_setting", "srcPath": "./components/app_setting" },
    { "name": "collect_personal_info", "srcPath": "./components/collect_personal_info" },
    { "name": "feedback", "srcPath": "./components/feedback" },
    { "name": "membership", "srcPath": "./components/membership" },
    { "name": "module_ui_base", "srcPath": "./components/module_ui_base" }
  ]
}

每个模块通过 oh-package.json5 声明其依赖。以 home 特性模块为例,它依赖多个组件层模块和通用层模块,形成清晰的单向依赖链:

json5 复制代码
// features/home/oh-package.json5
{
  "name": "home",
  "version": "1.0.0",
  "main": "Index.ets",
  "dependencies": {
    "commonlib": "file:../../commons/commonlib",
    "bill_manage": "file:../../components/bill_manage",
    "bill_data_processing": "file:../../components/bill_data_processing",
    "bill_base": "file:../../components/bill_base",
    "bill_card": "file:../../components/bill_card",
    "lib_network": "file:../../commons/lib_network",
    "dayjs": "1.11.13"
  }
}

再来看 entry 模块的依赖声明------它作为应用入口,依赖了所有特性模块和部分组件模块:

json5 复制代码
// products/entry/oh-package.json5
{
  "name": "entry",
  "version": "1.0.0",
  "main": "",
  "dependencies": {
    "commonlib": "file:../../commons/commonlib",
    "lib_network": "file:../../commons/lib_network",
    "home": "file:../../features/home",
    "statistics": "file:../../features/statistics",
    "assets": "file:../../features/assets",
    "mine": "file:../../features/mine",
    "bill_data_processing": "file:../../components/bill_data_processing",
    "aggregated_login": "file:../../components/aggregated_login",
    "app_setting": "file:../../components/app_setting",
    "feedback": "file:../../components/feedback",
    "membership": "file:../../components/membership",
    "collect_personal_info": "file:../../components/collect_personal_info",
    "dayjs": "1.11.13"
  }
}

3. 跨模块引用路径设计

在鸿蒙 HAR 包中,通过 oh-package.json5 中的 "main": "Index.ets" 统一导出入口,外部模块只需引用包名而非深层路径。以 commonlib 为例,其 Index.ets 统一导出了所有工具类和组件:

typescript 复制代码
// commons/commonlib/Index.ets
export { Logger } from './src/main/ets/utils/logger/Index';
export { WindowUtil } from './src/main/ets/utils/window/Index';
export { FrameworkUtil, TabIndexMap } from './src/main/ets/utils/framework/Index';
export { EventBus, EventKey } from './src/main/ets/utils/eventbus/Index';
export { contextUtil } from './src/main/ets/utils/ContextUtil';
export { ShareUtil } from './src/main/ets/utils/ShareUtil';
export * from './src/main/ets/utils/userinfo/Index';
export * from './src/main/ets/utils/router/Index';
export {
  BreakpointTypeEnum, BreakpointType, BreakpointModel,
  BreakpointStorage, BreakpointSystem,
} from './src/main/ets/utils/BreakpointSystem';
export { CommonConstants } from './src/main/ets/constants/CommonConstants';
export { CommonHeader } from './src/main/ets/components/CommonHeader';
export { CommonMonthPicker } from './src/main/ets/components/CommonMonthPicker';
export { CommonDayPicker } from './src/main/ets/components/CommonDayPicker';
export { CommonButton, CustomButtonType } from './src/main/ets/components/CommonButton';
export { ContainerRow } from './src/main/ets/components/ContainerRow';
export { ContainerColumn } from './src/main/ets/components/ContainerColumn';
export { CommonDivider } from './src/main/ets/components/CommonDivider';
export { ConfirmParam } from './src/main/ets/dialogs/CommonConfirmDialog';
export { ReminderUtil } from './src/main/ets/utils/ReminderUtil';
export { ReminderSettingVM } from './src/main/ets/utils/ReminderSettingVM';

其他模块通过包名直接引用:

typescript 复制代码
// features/home/src/main/ets/viewmodels/HomeVM.ets
import {
  ConfirmParam, EventBus, EventKey, RouterModule, WindowUtil
} from 'commonlib';
import { AccountingDB, BillProcessingModel } from 'bill_data_processing';
import { BalanceChangeType, BalanceResourceModel, ResourceUtil } from 'bill_base';

这种设计带来了几个好处:引用路径简短且统一、重构时只需修改 Index.ets 的导出、对外隐藏内部实现细节。

4. 面包屑导航与路由设计

MoneyTrack 使用基于 Navigation 组件的路由体系,通过 commonlib 中的 RouterModule 统一管理页面栈:

typescript 复制代码
// commons/commonlib/src/main/ets/utils/router/Router.ets
export class RouterModule {
  private static _stack: NavPathStack = new NavPathStack();

  public static getStack() {
    return RouterModule._stack;
  }

  public static push(info: NavRouterInfo, animated?: boolean) {
    RouterModule._stack.pushPathByName(info.url, info.param, info.onPop, animated);
  }

  public static replace(info: NavRouterInfo) {
    RouterModule._stack.replacePathByName(info.url, info.param);
  }

  public static pop(result?: Object, animated?: boolean) {
    RouterModule._stack.pop(result, animated);
  }

  public static clear(animated?: boolean) {
    // 清除栈中所有页面
  }
}

每个模块在 resources/base/profile/ 目录下维护自己的 router_map.json,实现了路由配置的模块自治。

项目案例

根目录 build-profile.json5d:\HarmonyOS\WorkSpace\MoneyTrack1.0.3\build-profile.json5)完整展示了 21 个模块的注册配置。以 bill_data_processingcommonlib 为例,前者封装数据库处理逻辑(包含关系型数据库 AccountingDBBaseDB),后者提供路由、EventBus、断点系统等通用能力------这种分层使得替换底层实现时无需修改上层代码。

实际的跨模块调用示例------在 HomeVM 中通过 EventBus 监听全局刷新事件:

typescript 复制代码
public async initData() {
  await this.dataProcessing.accountProcessing.init();
  await this.refreshBill();
  EventBus.instance.on(EventKey.GLOBAL_REFRESH_EVENT, () => {
    this.refreshBill();
  });
}

EventBus 的底层实现基于 AbilityKit 的 EventHub 能力,代码极其简洁:

typescript 复制代码
export class EventBus {
  private _eventHub = (contextUtil.getContext() as common.UIAbilityContext).eventHub;
  private static _instance: EventBus;

  public static get instance(): EventBus { /* 单例实现 */ }

  public emit(eventKey: EventKey | string, ...args: Object[]) {
    this._eventHub.emit(eventKey, ...args);
  }

  public on(eventKey: string, callback: Function) {
    this._eventHub.on(eventKey, callback);
  }

  public off(eventKey: string, callback?: Function) {
    this._eventHub.off(eventKey, callback);
  }
}

最佳实践与注意事项

1. 坚持单向依赖原则

依赖方向必须严格遵循:通用层 ← 组件层 ← 特性层 ← 入口层。绝不允许下层反向依赖上层。例如 commonlib 不依赖任何业务模块,bill_base 只能依赖 commonlib 而不能依赖 home。这种约束通过 oh-package.json5 的依赖声明即可强制执行。

2. 避免循环依赖

循环依赖是模块化架构中最常见也最隐蔽的问题。例如 模块A 依赖 模块B,而 模块B 又直接或间接依赖 模块A,会导致编译失败或运行时异常。在实践中,应将可能产生双向依赖的共用类型抽离到更底层的模块(如 bill_basecommonlib)中。在 MoneyTrack 中,bill_base 承担了"基础类型仓库"的角色,所有账单相关的枚举、接口和数据模型都在此定义。

3. 接口隔离与最小化导出

每个 HAR 模块应通过 Index.ets 精确控制对外暴露的接口,避免将内部实现细节暴露给外部。遵循"最少知识原则"------只导出外部确实需要引用的类、函数和类型。同时,对于跨模块共享的 ViewModel 和 Model,使用 @ObservedV2@Trace 装饰器确保响应式状态在模块间正确传递。

4. 模块粒度控制

模块拆分不宜过细也不宜过粗。过细会导致大量小模块管理成本上升,过粗则失去了模块化的意义。实践中,可按"业务聚合度"为标准:一组高内聚的、可能被多个上层复用的功能,应独立为一个 HAR 模块。MoneyTrack 中 21 个模块的粒度就是经过多次迭代后沉淀下来的最优解。

5. 版本管理一致化

所有 HAR 模块应保持版本号一致(推荐跟随主应用版本),避免出现"模块版本矩阵"导致的依赖冲突。在根目录统一管理依赖版本,子模块通过 file: 协议引用本地模块时不需要额外关注版本匹配问题。

总结

多模块 HAR 架构是鸿蒙大型应用工程的基石。MoneyTrack 通过四层架构的精心设计,实现了 21 个模块的高内聚低耦合。通用层提供基础设施,组件层封装可复用的业务组件和数据模型,特性层聚合领域页面,入口层完成最终组装。配合单向依赖原则、接口隔离和统一的包导出设计,这一架构支撑了项目的快速迭代和团队高效协作。在实际项目中,应根据业务复杂度灵活调整模块粒度,始终将"可维护性"和"可扩展性"作为架构设计的首要目标。

参考文档

  • oh-package.json5 配置指南
  • HAR 包构建与引用规范
  • HarmonyOS 多模块工程最佳实践
  • 项目源文件:d:\HarmonyOS\WorkSpace\MoneyTrack1.0.3\build-profile.json5
  • 各模块 oh-package.json5 示例:features/home/oh-package.json5products/entry/oh-package.json5
相关推荐
爱勇宝1 小时前
《道德经》第5章:生产环境不相信眼泪
前端·后端·架构
g_z_x_2 小时前
鸿蒙 ArkTS 实战:Quiz Arena 从状态建模到交互闭环完整解析
华为·交互·harmonyos
Splashtop高性能远程控制软件2 小时前
本地部署 vs 公有云 vs 混合架构:一份可对照落地的 IT 架构选型指南
运维·架构·远程控制·splashtop
用户0934077735142 小时前
HarmonyOS WPS Open SDK 实践:最小打开闭环与 enableEdit 分支处理
typescript·harmonyos
绝世番茄2 小时前
鸿蒙原生 ArkTS 布局方式之 Stack 悬浮按钮布局(HarmonyOS NEXT / API 24)
华为·harmonyos·鸿蒙
Sirius Wu2 小时前
OpenClaw 并发安全完整详解
网络·人工智能·安全·ai·架构·aigc
2301_768103493 小时前
HarmonyOS趣味相机实战第5篇:CameraKit资源释放、PixelMap生命周期与预览黑屏排查
性能优化·harmonyos·arkts·camerakit·pixelmap
Coffeeee3 小时前
Android功耗优化,哪些代码会让你的机子发热又耗电
android·架构·kotlin
拥抱太阳06163 小时前
HarmonyOS 应用开发《掌上英语》第7篇-项目演进路线从单页面到多模块架构升级
架构