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

在这里插入图片描述

概述

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

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

核心知识点

1. 四层分层架构设计

MoneyTrack 将 21 个 HAR 模块划分为四个层次,层与层之间遵循严格的单向依赖关系:

通用层 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 个模块被逐一注册。以下展示完整的模块注册配置:

{
  "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 特性模块为例,它依赖多个组件层模块和通用层模块,形成清晰的单向依赖链:

// 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 模块的依赖声明——它作为应用入口,依赖了所有特性模块和部分组件模块:

// 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 统一导出了所有工具类和组件:

// 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';

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

// 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 统一管理页面栈:

// 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 监听全局刷新事件:

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

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

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
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐