【听见课堂 HarmonyOS NEXT 实战系列 02】Stage Model 多模块架构:entry、HAR 与 HSP 如何分工

当 HarmonyOS NEXT 项目从单页 Demo 发展成完整应用后,最容易出现的问题不是“代码写不出来”,而是页面、模型、主题、数据库和系统能力全部挤在 entry 模块里。短期看起来开发很快,后期却会遇到循环依赖、重复类型、组件难复用和安装产物关系不清等问题。

听见课堂从项目结构上把职责拆成四个模块:entrycommon-corecommon-uishared-business。这篇文章以真实工程为例,说明 Stage Model 下如何建立清晰、可验证的模块边界。

听见课堂 Stage Model 多模块架构

一、四个模块分别解决什么问题

项目的模块关系可以概括为:

entry
├─ depends on common-core
├─ depends on common-ui
└─ depends on shared-business
      └─ depends on common-core

common-core   无业务 UI 依赖
common-ui     无业务数据依赖

entry、common-core、common-ui 与 shared-business 依赖关系

各模块职责如下:

模块 主要职责 不应该放入的内容
entry UIAbility、页面外壳、交互状态、路由入口 数据库表结构、复杂聚合规则
common-core 领域模型、路由常量、用户偏好 具体页面、设备能力实现
common-ui 颜色、尺寸、背景等共享视觉资产 课程业务规则、数据库访问
shared-business Service、Repository、持久化、系统能力封装 页面布局和临时 UI 状态

边界越清楚,依赖方向越容易保持单向。尤其是 common-core,它应该成为其他业务模块都能理解的稳定语言,而不是反向依赖页面或数据库实现。

二、entry 应该轻,但不是空壳

entry 仍然负责 Stage Model 的应用入口。听见课堂在 EntryAbility.ets 中先初始化课堂数据,再加载首页内容:

onWindowStageCreate(windowStage: window.WindowStage): void {
  this.initializeDataAndLoad(windowStage);
}

private async initializeDataAndLoad(windowStage: window.WindowStage): Promise<void> {
  const usingRelationalStore: boolean = await initializeClassroomData(this.context);
  hilog.info(DOMAIN, TAG, 'RelationalStore active: %{public}s',
    usingRelationalStore ? 'true' : 'false');
  windowStage.loadContent('pages/Index');
}

这里有两个设计点:

  1. 数据初始化属于进入应用前的基础工作,但具体数据库创建和种子数据不写在 Ability 内;
  2. initializeClassroomData 返回当前是否启用了 RelationalStore;如果创建失败,内部切换到内存 Repository,再继续加载页面,避免把一次持久化异常直接变成白屏。

Ability 还要承担前后台生命周期协调。例如项目在进入后台时释放手语相机资源,这类和应用生命周期直接相关的动作,放在入口层比散落在页面里更可靠。

三、HAR 与 HSP 不是同一种复用

在这个项目中,三个公共模块的产品形态并不完全相同:

  • common-corecommon-ui 适合作为 HAR,被编译进使用它们的模块;
  • shared-business 作为 HSP,承载可共享的业务与能力实现。

HAR 更接近编译期复用,使用方最终会包含相关代码;HSP 则是独立共享包,运行时由应用模块引用。两者的差别会直接影响安装和交付。

例如在全新设备上只安装 entry 产生的 HAP,而没有安装它依赖的 HSP,应用可能无法完成正常启动。因此真机验证不能只写一条“安装 HAP 成功”,还要记录产物之间的依赖顺序。

一个更稳妥的设备验证顺序是:

hdc list targets -v
hdc install -r .\shared-business\build\default\outputs\default\shared-business-default-signed.hsp
hdc install -r .\entry\build\default\outputs\default\entry-default-signed.hap

实际路径应以当前 hvigor 输出为准。命令写进文章不代表本次写作过程中已经重新安装,交付时仍要保留真实终端输出。

四、模块依赖应该由包配置明确表达

entry/oh-package.json5 中通过本地模块路径声明依赖:

{
  "dependencies": {
    "@heard/common-core": "file:../common-core",
    "@heard/common-ui": "file:../common-ui",
    "@heard/shared-business": "file:../shared-business"
  }
}

shared-business 只依赖 common-core。这能避免业务层为了使用颜色或页面组件而反向依赖 UI 模块,也避免核心模型知道数据库或设备实现。

建议为每个模块保留一个统一导出入口,例如:

// common-core/src/main/ets/index.ets
export * from './models/ClassroomModels'
export * from './navigation/Routes'
export * from './preferences/UserPreferences'

统一出口能减少跨模块调用方对内部文件位置的依赖。未来移动内部目录时,只要公共导出保持稳定,页面代码通常不需要跟着大面积修改。

五、不要把“共享”理解成“什么都往里放”

公共模块最常见的失控方式,是出现一个不断膨胀的 utilscommon。判断代码应该放在哪个模块,可以问三个问题:

  1. 它表达的是稳定领域概念,还是具体界面?
  2. 它是否需要系统上下文、数据库或设备能力?
  3. 其他模块依赖它后,会不会反向引入不需要的实现?

例如:

  • RouteIdTaskItem 是稳定领域语言,放在 common-core
  • AppColorsAppSizes 是共享视觉 token,放在 common-ui
  • RelationalClassroomRepository 需要关系型数据库能力,放在 shared-business
  • 页面选择了哪个 Tab、弹窗是否展开,是短生命周期状态,留在 entry

六、构建通过后,还要验证模块产物

多模块项目的验证至少包括以下内容:

ohpm install
.\hvigorw.bat assembleHap --mode module -p module=entry@default
.\hvigorw.bat assembleApp

除了命令是否返回成功,还要检查:

  • entry、HAR、HSP 是否都生成了预期产物;
  • HSP 和 HAP 的包名、版本与签名是否匹配;
  • 全新设备上能否按正确顺序安装并冷启动;
  • 从后台恢复时,相机、语音等资源是否按生命周期重新建立;
  • 文章或交付说明中是否把“构建成功”和“真机运行成功”分开描述。

听见课堂现有结构已经体现了这四层职责,但模块边界仍需要通过持续审查来维护。只要页面开始直接访问数据库,或 common-core 开始依赖 ArkUI 页面,就说明依赖方向正在变坏。

总结

Stage Model 的多模块架构不是为了让目录看起来更专业,而是为了把生命周期、领域语言、视觉资产和业务实现分开管理。HAR 与 HSP 也不只是两种打包后缀,它们会影响依赖方式、产物结构和真机安装流程。

下一篇将沿着页面到数据源的路径继续深入,分析为什么 ArkUI 页面不应该直接操作数据库,以及 Service 与 Repository 如何共同保证业务口径一致。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐