20 KiB
AR10 XApp Removable Packaging Specification
版本:V1.2 日期:2026-09-18
性质:规范性文件(normative)
适用对象:xapp,即一级业务包
关联文档:
AR05_工作台架构约定.mdAR09_对象命名规范.mdAR11_技能专员连接器可删除封装规范.md
1. 目标
本文定义 xapp 的封装方式,目标是让一个 xapp 在工程上达到如下删除标准:
- 删除一个后端目录
- 删除一个前端目录
- 删除一条注册项
- 不需要全仓手工搜改业务代码
- 重新构建后不出现编译错误
- 运行卸载脚本后,不残留该
xapp自有表、路由、菜单、任务、通知、前端状态
本文中的“可删除”指:
- 删一个目录 + 删一条注册项 + 跑一次卸载 = 安全移除一个
xapp
严格意义上,“删一个文件就彻底删除”只适用于插件二进制或代码生成包;在当前单仓工程内,合理目标应为“删一个目录”。
2. 范围与非目标
2.1 本文覆盖
本文覆盖 xapp 的以下封装维度:
- manifest
- registry
- 前后端路由注册
- provider 暴露
- 数据边界
- schema 与 seed
- 卸载协议
- 定义条目所有权
- 共享引用降级协议
- 守卫与验收标准
2.2 本文不覆盖
本文不讨论以下内容的具体业务实现:
- 某个
xapp内部页面应该长什么样 - 某个
xapp的领域模型如何详细设计 - 某个
xapp的 UI 风格如何命名
这些属于对象内部实现,不属于封装边界规范本身。
3. 当前问题
当前仓库已经存在 xapp_definition、前端 /xapps/... 路由以及若干 XAppShell,但多数业务 xapp 仍停留在“入口像 xapp,底层仍是平台散装模块”的阶段。典型表现如下:
- 平台总路由中仍手写某个
xapp的业务路由 - 平台总导航中仍手写某个
xapp的菜单 - 平台层 API 直接查询某个
xapp的表 xapp的核心对象仍放在通用model包,而非xapp自己的域包- 持久化模型直接兼任 API DTO 和领域对象
- 统计、画像、后台管理直接耦合业务表,而不是通过 provider 聚合
xapp的任务、通知、缓存、前端状态未形成 own 命名空间xapp_definition、目录条目、seed 定义记录等定义层资源尚未进入卸载协议- 历史任务、收藏、通知历史等共享引用在对象删除后的处理策略未定义
这意味着当前不少 xapp 仍停留在“挂了 xapp 皮肤的并行模块”阶段,尚未达到可插拔业务包标准。
4. 定义
4.1 什么是 xapp
在本项目中,xapp 的定义是:
- 一个可注册的一级业务包
- 一个有 manifest 的对象
- 一个有自有 schema 的领域边界
- 一个只通过 contract 暴露能力的模块
- 一个可以通过“删目录 + 删注册 + 跑卸载”安全移除的工程单元
4.2 什么不是 xapp
以下对象不应按 xapp 方式建模:
- 单个技能
- 单个专员
- 单个连接器
- 一张单页配置页
- 一个普通 store 或 API 文件
这些对象的封装规则见 AR11。
5. 封装目标
每个 xapp 必须完整拥有以下六类内容:
- Manifest
- Frontend shell
- Backend module
- Domain model / repo / service
- Owned schema
- Integration adapters
平台层只负责:
- 注册
xapp - 聚合
xapp暴露的能力 - 为
xapp提供公共底座能力
平台层不负责:
- 直接进入
xapp内部目录取对象 - 直接查询
xapp自有表 - 直接 hardcode 某个
xapp的页面、菜单、统计、通知 - 直接依赖
xapp内部 repo / service / views
6. 依赖方向
xapp 封装能否成立,核心不在于目录是否漂亮,而在于依赖方向是否单一。
正确依赖方向应为:
platform core
-> xapps/core/contracts
-> xapps/core/registry
-> xapp manifest
-> xapp module install / register
xapp internal
-> xapp domain / repo / service / dto / schema
错误依赖方向包括:
platform api -> xapp repoplatform stats -> xapp tableplatform nav -> xapp views path literalplatform seed -> xapp domain object
一句话:平台层可以认识“这个 xapp 存在”,但不能认识“这个 xapp 里面具体有什么文件”。
7. 推荐目录结构
7.1 后端
backend-go/internal/xapps/
core/
contracts.go
manifest.go
registry.go
uninstall.go
apps/
internal_exam/
manifest.go
module.go
api/
student.go
admin.go
domain/
paper.go
record.go
mistake.go
repo/
paper_repo.go
record_repo.go
mistake_repo.go
service/
exam_service.go
stats_service.go
dto/
request.go
response.go
schema/
migrations.go
uninstall.go
assets/
tests/
internal_training/
manifest.go
module.go
api/
domain/
repo/
service/
dto/
schema/
assets/
tests/
7.2 前端
frontend/src/xapps/
core/
contracts.js
registry.js
installer.js
uninstall.js
apps/
internal-exam/
manifest.js
routes.js
nav.js
providers.js
api/
store/
views/
components/
assets/
internal-training/
manifest.js
routes.js
nav.js
providers.js
api/
store/
views/
components/
assets/
8. 核心硬规则
8.0 一对象一目录是最终形态
对 xapp 而言,最终封装形态必须满足:
- 一个
xapp对应一个后端目录 - 一个
xapp对应一个前端目录 - 该
xapp的业务定义、路由定义、provider 定义、schema 定义都落在自己的目录中
因此:
- 允许中心化注册
- 不允许中心化定义
这里的“中心化注册”指:
- registry 统一列出有哪些
xapp manifest - installer 统一执行 install / uninstall
- platform core 统一聚合 provider
这里的“中心化定义”指:
- 在一个平台公共文件里手写多个
xapp的业务路由 - 在一个平台公共文件里手写多个
xapp的菜单项 - 在一个平台公共文件里手写多个
xapp的 schema / provider / page path
前者是允许的,后者不是最终封装形态。
8.0.1 允许中心化 / 禁止中心化
| 类型 | 是否允许 | 说明 |
|---|---|---|
xapps/core/registry 集中列出 manifest |
允许 | 该文件承担注册中心职责 |
xapps/core/installer 统一安装路由和导航 |
允许 | 这是装配层 |
平台总路由手写 internal-exam 业务路径 |
禁止 | 这是业务定义泄漏 |
平台总导航手写 internal-training 菜单细节 |
禁止 | 这是业务定义泄漏 |
平台公共文件集中维护多个 xapp 的 page path / provider key / table 名 |
禁止 | 这会破坏目录级删除 |
8.1 平台层不能直接 import xapp 内部实现
平台层只能依赖:
xapps/core/registrymanifestcontracts/interface
平台层不能依赖:
internal_exam/domain/*internal_exam/repo/*internal_exam/service/*internal_exam/views/*- 任何其他
xapp内部文件
这条规则的目的,是保证删除某个 xapp 目录后,不会炸掉全局依赖图。
8.2 路由必须由 xapp 自己注册
总路由不得手写某个 xapp 的业务路径,例如:
/api/exam/*/xapps/internal-exam/*/courses/*/products/*
正确做法:
- 平台加载
xapp registry xapp在module.go/manifest.js中自注册前后端路由
后端示意:
xappRegistry.Register(internalexam.Manifest)
前端示意:
xappRegistry.register(internalExamManifest)
8.3 数据表必须归属到 xapp 命名空间
为了实现“删目录即可删表”,表名必须体现归属。
不推荐:
exam_paperexam_recordmistake_recordlearning_progress
推荐:
xapp_exam_paperxapp_exam_recordxapp_exam_mistakexapp_training_progress
如果某张表是平台共享能力,而不是某个 xapp 私有数据,则应放入 shared 域,不允许伪装成 xapp 私有对象。
8.4 核心对象不能继续停留在通用 model 总包
xapp 私有业务对象必须放入自己的域目录,例如:
internal/xapps/apps/internal_exam/domain/
平台 internal/model 中只保留:
- 平台公共对象
- 跨
xapp共享对象 - 公共底座对象
8.5 持久化模型不能直接充当 API DTO
必须分层:
domain:领域对象repo entity:持久化对象dto request/response:接口契约
禁止继续使用如下模式:
ShouldBindJSON(&model.ExamPaper{})- API 直接返回 ORM entity
- 统计逻辑直接依赖表字段细节
8.6 统计、画像、后台管理只能通过 provider 聚合
平台层不得直接查询某个 xapp 的私有表来构建:
- 首页统计
- 学员画像
- 部门统计
- 后台管理报表
应改为由 xapp 对外暴露 provider:
type XAppStatsProvider interface {
BuildAdminStats(ctx context.Context) (any, error)
}
type XAppProfileProvider interface {
BuildUserProfile(ctx context.Context, userID uint) (any, error)
}
平台只聚合 provider 输出,不碰 xapp 内部表结构。
8.7 菜单、通知、任务、前端状态必须归 xapp 自己管理
不得把以下内容散落在平台全局文件中:
- 导航项
- 页面路由 path 判断
- 通知跳转链接
- 定时任务
- seed 数据入口
- localStorage key
- 前端 layout 的特殊 case
这些都必须由 xapp manifest 暴露。
8.8 定义条目也属于 xapp 的 own boundary
xapp 的封装边界不仅包括代码、路由、表和缓存,也包括“定义层资源”。
定义层资源至少包括:
xapp_definition中属于该xapp的定义记录- 对象目录、市场目录、对象中心中的条目
- seed 自动生成的对象定义记录
- 前端目录页、治理页中依赖定义中心生成的条目
如果一个 xapp 删除后,这些定义条目仍然残留,那么:
- 用户仍可能看到僵尸入口
- 后台仍可能显示无效定义
- 平台仍可能尝试按已删除对象做跳转或查询
因此,定义条目必须被视为 owned resource,纳入 manifest 和卸载协议。
9. Manifest 与 Registry 设计
9.1 后端 manifest
type XAppManifest interface {
Key() string
Meta() XAppMeta
RegisterRoutes(r gin.IRouter)
RegisterProviders(reg ProviderRegistry)
RegisterMigrations(reg MigrationRegistry)
RegisterSeeds(reg SeedRegistry)
RegisterJobs(reg JobRegistry)
RegisterNotifications(reg NotificationRegistry)
OwnedTables() []string
OwnedStorageKeys() []string
OwnedJobKeys() []string
OwnedNotificationKeys() []string
OwnedDefinitionKeys() []string
OwnedCatalogEntries() []string
SharedReferencePolicy() SharedReferencePolicy
}
后端 manifest 至少负责:
- 路由注册
- provider 注册
- migration 注册
- seed 注册
- job 注册
- 通知注册
- 定义条目归属声明
- 共享引用处理策略声明
9.2 前端 manifest
export default {
key: 'internal-exam',
label: '内部考试APP',
baseRoute: '/xapps/internal-exam',
install({ navRegistry, routeRegistry, providerRegistry, storeRegistry }) {},
uninstallMeta: {
ownedStorageKeys: ['xapp:internal-exam:*'],
ownedRouteNames: ['InternalExam*'],
ownedNavKeys: ['xapp.internal-exam.*'],
ownedDefinitionKeys: ['xapp.internal-exam.definition'],
ownedCatalogEntries: ['catalog.xapp.internal-exam'],
},
}
前端 manifest 至少负责:
- 页面路由注册
- 菜单注册
- store 注册
- provider 注册
- 前端状态资源声明
- 前端定义条目资源声明
9.3 Registry 的职责
registry 只做两件事:
- 收集 manifest
- 统一安装 / 卸载 / 枚举
registry 不应承担:
- 业务逻辑
- repo 查询
- 页面渲染
- 领域对象转换
10. 卸载协议
“可删除”不是只删代码,还必须定义资源清理协议。
10.1 卸载输入
卸载协议至少应支持以下输入:
xapp keydry_rundrop_tablesclear_storageclear_jobsclear_notifications
10.2 卸载输出
卸载结果至少应返回:
- 实际清理的表
- 实际清理的 job key
- 实际清理的 notification key
- 实际清理的 storage key
- 未清理成功的残留项
- 实际清理的定义条目
- 被共享引用阻塞或降级处理的项
10.3 卸载原则
卸载必须遵守以下原则:
- 只清理 manifest 声明过的 owned 资源
- 默认支持
dry_run - 禁止越权删除共享资源
- 共享表必须由 shared 域自己维护,不得被 xapp 卸载误删
10.4 定义条目清理协议
卸载 xapp 时,必须同时清理或冻结以下定义层资源:
xapp_definition中的定义记录- 对象目录或市场目录中的条目
- seed 自动生成的默认定义条目
允许的处理方式包括:
- 直接删除
- 标记为 tombstone
- 标记为 disabled 且不再对用户可见
但无论采用哪种方式,都必须保证:
- 平台不会再把它当成可安装、可进入、可查询的有效
xapp - 前后端目录与导航中不再出现死入口
10.5 Shared Reference Policy
xapp 删除时,除 own resource 外,还必须处理共享域对它的历史引用。
典型共享引用包括:
- 历史任务记录
- 项目绑定关系
- 收藏 / 最近使用
- 通知历史
- 审计日志
- 历史发布版本
规范允许以下四类处理策略:
block_uninstall:仍存在强引用时禁止卸载convert_to_tombstone:转为“对象已移除”墓碑态detach_reference:解除引用但保留历史记录readonly_history:保留只读历史,不允许继续进入对象
xapp manifest 必须声明自己的共享引用处理策略,平台卸载器必须在 dry_run 结果中明确报告:
- 哪些引用会被阻塞
- 哪些引用会被降级
- 哪些引用会被直接解除
10.6 Dry-run Gate 与执行守卫
卸载协议不仅要能执行,还必须能被平台守卫。
至少应具备以下守卫:
- uninstall
dry_run报告 - owned resource audit
- 定义条目完整性检查
- 共享引用阻塞检查
- CI 中的封装边界检查
11. Owned 资源矩阵
一个 xapp 的 own boundary 至少应覆盖以下资源:
| 资源类型 | 是否必须声明 | 典型例子 |
|---|---|---|
| 后端目录 | 必须 | backend-go/internal/xapps/apps/internal_exam/ |
| 前端目录 | 必须 | frontend/src/xapps/apps/internal-exam/ |
| 后端路由 | 必须 | /api/xapps/internal-exam/* |
| 前端路由 | 必须 | /xapps/internal-exam/* |
| owned tables | 必须 | xapp_exam_record |
| owned storage keys | 必须 | xapp:internal-exam:* |
| definition keys | 必须 | xapp.internal-exam.definition |
| catalog entries | 必须 | catalog.xapp.internal-exam |
| provider keys | 必须 | xapp.internal-exam.stats |
| job keys | 视需要 | xapp.internal-exam.daily-sync |
| notification keys | 视需要 | xapp.internal-exam.record-published |
| nav keys | 必须 | xapp.internal-exam.entry |
12. 当前培训 / 考试域的拆分建议
12.1 应进入 internal_exam xapp 的内容
应归入:
- 考试配置
- 考试记录
- 错题本
- 发证逻辑
- 学员考试流程
- 考试统计 provider
- 考试后台管理接口
- 考试前端 views / api / store
当前对象对应:
ExamPaperExamRecordMistakeRecord
12.2 应进入 internal_training xapp 的内容
应归入:
- 课程
- 产品知识
- 公司介绍
- 培训入口
- 培训前端 views / api / store
- 培训进度统计 provider
12.3 LearningProgress 的边界
LearningProgress 当前记录的是:
companyproductcourse
因此它不应进入 internal_exam。
它有两种合理归属:
- 归入
internal_training - 归入 shared learning 域
选择标准如下:
- 如果未来只有培训
xapp使用,则归internal_training - 如果未来知识库、认证、课程中心等多个
xapp都会消费,则归 shared learning
13. 迁移顺序
推荐按以下顺序实施,避免边搬边炸:
- 先建立
xapps/core/contracts - 建立 registry
- 让
exam / training先改为 manifest 注册 - 将平台直查表的逻辑改为 provider 调用
- 再搬迁
domain / repo / service - 再把 DTO 与持久化对象分层
- 最后再改表名命名空间
- 最后补卸载脚本
这个顺序的核心原因是:
- 先修正依赖方向
- 再修正代码落点
- 再修正对象边界
- 最后修正资源所有权
14. 判定红线
如果一个 xapp 还存在以下任意一条,则不算完成封装:
- 总路由中仍手写它的业务路径
- 总导航中仍手写它的业务菜单
- 平台
api/*.go仍直接查询它的私有表 - 平台
model总包仍承载它的核心业务对象 - 别的模块还能直接 import 它的内部包
- 数据表名未进入它 own 的命名空间
- 统计、画像、后台管理未通过 provider 输出
- 前端主 layout 仍有针对它的硬编码 path 判断
- 卸载协议无法枚举它的 owned 资源
- 删除目录后仍需手工搜索多处散装注册点
- 一个公共定义文件里仍集中维护多个
xapp的业务路由、菜单、provider、schema 或 path xapp_definition、目录条目或 seed 定义记录未进入 own boundary- 历史任务、收藏、通知历史等共享引用没有明确降级策略
- 缺少
dry_run、边界检查或资源审计守卫
14.1 迁移期兼容层的限制
本规范允许迁移期存在少量兼容层,但必须满足以下条件:
- 兼容层只能做转发、注册、兼容映射
- 兼容层不得继续承载多个
xapp的业务定义 - 兼容层必须有明确退场目标,不能成为长期正式结构
换句话说:
registry可以中心化compat adapter可以暂存definition hub不允许长期存在
14.2 Enforcement / Guard
为了防止封装边界回退,平台至少应建立以下自动守卫:
- import boundary lint:禁止平台层直接 import
xapp内部实现 - registry completeness check:已注册对象必须能完整枚举 own 资源
- owned resource audit:检查 manifest 声明和实际资源是否一致
- uninstall dry-run gate:卸载前必须可生成风险报告
- CI fail 条件:若出现未声明资源、未处理共享引用或越权依赖,则直接失败
15. 验收清单
一个 xapp 只有满足以下条件,才算达到“整包可删除”:
- 删除
backend-go/internal/xapps/apps/<xapp>/ - 删除
frontend/src/xapps/apps/<xapp>/ - 删除 registry 里的注册项
- 后端构建通过
- 前端构建通过
- 没有平台层 import 残留
- 没有平台级 hardcoded path 残留
- 运行卸载脚本后,该
xapp的 owned tables 被清理 - 前端导航、页面、缓存、localStorage 全部消失
- 平台统计只少这一个
xapp的 provider 结果,不出现空指针或编译错误 - job / notification / provider key 不残留
- 共享域不被误删
xapp_definition、目录条目、seed 定义条目不会残留死入口dry_run报告能正确列出 shared reference 的阻塞与降级结果
16. 最终标准
xapp 的最终定义应为:
- 一个可注册的业务包
- 一个有 manifest 的对象
- 一个有自有 schema 的领域边界
- 一个只通过 contract 暴露能力的模块
- 一个可以通过“删目录 + 删注册 + 跑卸载”安全移除的工程单元
只有达到这个标准,xapp 才能成为真正的一级对象。