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