业务故事站
P2 Foundry

组件市场与代码仓库:资产分发与口径复用

组件市场(Marketplace)把本体对象 / 指标 / 仪表盘 / 应用 / Notebook / Fusion 项目打包成商品,跨环境一键分发安装;代码仓库把 SQL 片段、模板、宏定义版本化沉淀,"{{macro:name}}" 一处维护、SQL 工作台与 Notebook 多处复用。看完这 4 个故事,你就知道怎么把已验证的资产和口径真正沉淀下来。

数据工程师 平台管理员 业务分析师 组件市场 代码仓库 SQL 宏 共 4 个故事

能 / 不能速览

✅ 这个主题能做
  • 六类来源发布商品:object 出 YAML 信封,metric/dashboard/app/notebook/fusion_project 读库 JSON 组包
  • 跨环境一键安装:object→ImportObjects、metric 四层重建、重名加 _imported_<ts> 后缀、下载计数 +1
  • 同 rid 重新发布版本 +1,历史安装记录保留;unlisted 商品拒绝安装
  • 代码仓库三类(snippet/template/macro):文件版本覆盖归档(fcr_revisions)、删除级联
  • 宏展开 {{macro:name}}:宏表优先、未命中原样、嵌套迭代 maxMacroDepth=5;SqlWorkbench 与 Notebook 两处生效
⛔ 这个主题做不了
  • 无商品审核流、无商品依赖解析;历史 payload 快照不保留(版本+1 只替换 payload)
  • 重发布不自动推送 / 不自动升级已安装环境,需手动重新 install
  • 代码仓库无 git 协议、无版本 Diff、无文件重命名(path 不可改)
  • 宏名全局按 path 匹配,无仓库级命名空间;宏展开仅 query.Execute 翻译前(AIP NLQ / 报表 SQL 不消费)
  • 仓库无 owner 权限点,任何登录用户可改任意仓库;并发覆盖无乐观锁

适用角色

本主题面向三个角色:

  • 数据工程师:发布 / 安装商品、维护代码仓库与宏定义——是核心使用者。
  • 平台管理员 / 部署工程师:把控生产环境安装节奏、下架问题商品。
  • 业务分析师:在 SQL 工作台直接引用团队沉淀的宏,少写重复过滤段。

指标师 / 分析师通过商品市场把本环境验证过的资产变成团队的"资产货架"。

能力速览(能做什么)

商品发布

六类来源导出为自包含商品:object 走 ExportObject YAML 信封,metric/dashboard/app/notebook/fusion_project 读库 JSON 组包,跨环境引用全用名称规避 id 漂移。

一键安装

按类型物化到目标环境:object→ImportObjects(created=0 判失败)、metric 逐层重建 entity/dimension/measure/metric、JSON 类重名加 _imported_<ts> 后缀。

版本与状态

同 rid 重新 publish 版本 +1、payload 替换、安装记录保留;draft/published/unlisted 三态,unlisted 拒绝安装(唯一 4xx 前置校验)。

代码仓库

snippet/template/macro 三类仓库,(repo_id, path) 唯一,新版本覆盖、旧内容归档到 fcr_revisions,恢复历史=覆盖写入(新版本)。

宏展开链路

ResolveMacros 经 query.MacroResolver 钩子注入,Execute 翻译前展开;未命中不阻塞、宏可引用宏(深度 ≤5)、宏表优先。

调整指南(怎么调整)

  • 发布前写好 meta:name/description/tags 让商品在货架上可检索;author 缺省取当前用户。
  • 升级商品:同 rid 重新 publish 版本 +1,但不会推送已装环境——目标环境要手动重新 install 才拿到新产物。
  • 下架:PUT status=unlisted 即拒绝安装;重发布会强制置回 published。
  • 建宏:macro 仓库里 path=宏名、内容即宏体;模板放 template 仓库(不参与自动展开);改口径只改宏文件,全团队 SQL 自动生效。
  • 回退模板:前端"恢复"历史版本会生成新版本(当前内容进归档),历史完整保留,不是回到旧版本号。

做得好的场景

资产分发与口径沉淀让"复用"从口头约定变成平台能力,特别适合以下场景:
  • 跨环境迁移:开发环境验证过的指标 / 对象一键装到生产,比手工重建 + 核对口径快一个量级。
  • 资产货架:下载计数度量复用价值,团队知道哪类资产最抢手。
  • 口径收敛:AND region='华东' 这类过滤段收敛到一个宏文件,一处维护、处处生效。
  • 模板版本化:月报模板每版归档可回溯,回退口径有据可查(谁、何时、什么内容)。

限制与不足

以下是明确的边界,使用前先知道:
  • 市场无审核 / 无依赖解析:发布即上架;商品间依赖(指标依赖对象等)无显式声明,安装顺序由调用方保证。
  • 历史 payload 不保留:版本 +1 只替换 payload,历史只体现在安装记录(版本号 + target_ref)。
  • 对象绑定保真度部分:dashboard 图表 / app 组件的 object_type_id 按导出原值透传,目标环境 id 不同时可能错绑。
  • 无 git / 无 Diff / 无重命名:代码仓库是"覆盖归档"而非 commit 图;改名需"新建 + 删除"两步。
  • 宏作用域与消费范围:宏名全局匹配无命名空间;只在 query.Execute 翻译前展开(AIP NLQ、报表 SQL 不消费)。
  • 并发与权限:同文件并发 UpdateFile 无乐观锁;仓库无 owner 权限点,任何登录用户可增删改。

场景故事

故事 1 把验证过的 sales_amount 指标发布成商品,形成资产货架
背景
开发环境的指标师把 sales_amount(实体 sales_order_entity + region 维度 + total_amount 度量)验证好了,想让它变成团队资产。她打开"商品市场"页,从来源选指标、填商品信息,POST /marketplace/items 发布成 v1 商品——商品条目的 rid 按来源血缘唯一,之后重新发布就是同一商品的下一个版本。
传统做法对比
以前跨环境 / 跨团队搬指标靠手工重建一遍再核对口径,实体、维度、度量、指标定义四层逐一对照,少说半天。现在六类资产(object/metric/dashboard/app/notebook/fusion_project)一键组包成自包含商品,object 走 ExportObject 的 YAML 信封,其余读库 JSON 组包,跨环境引用全用名称规避内部 id 漂移。
角色
数据工程师 / 指标师(资产生产者);后续由部署工程师在目标环境安装。
操作步骤
  1. 打开"商品市场"(/foundry/marketplace),点发布
  2. 来源类型选 metric,来源 ID 填指标 api_name:sales_amount
  3. 填商品名 / 描述 / 标签(如"销售额指标"、"销售、指标"),点发布
  4. 在商品列表核对 rid、版本、状态与下载数
系统响应
POST /api/v1/marketplace/items 返回商品条目:
{ "code": 0, "data": {
  "id": 5, "rid": "marketplace:metric:sales_amount",
  "item_type": "metric", "name": "销售额指标",
  "version": 1, "status": "published", "downloads": 0, "author": "admin",
  "payload": { "format": "json", "content": {
      "entity": { "name": "sales_order_entity", ... },
      "object_type_name": "sales_order",
      "dimensions": [...], "measures": [...],
      "metric": { "name": "sales_amount", "measure": "total_amount", ... } } } } }
object 类型的商品 payload 是 YAML 信封:{"format":"yaml","content":"<zy-foundry/ontology/v1 YAML 文本>"}
结果洞察
跨环境引用全用名称:metric 的 object_type_name 指向对象 api_name、分子 / 分母用度量名,安装时在目标环境按名重解析内部 id;首次发布 version=1、downloads=0;同 rid 重新 publish 会 version+1 并更新同一条记录,安装记录保留。
调整建议
发布时把 meta(name/description/tags)写清楚,货架才好检索;来源名取不到时回退 sourceType:sourceID;六类来源里 fusion_project 依赖 fusion 服务注入,未启用时该类型发布会返回明确错误。
动手试一试
登录:admin / admin1。页面路径:商品市场 → 发布。输入内容:source_type=metric、source_id=sales_amount。预期结果:返回 item 且 rid=marketplace:metric:sales_amount、version=1、status=published。
限制提示
无商品审核流(draft 只是状态标记);无依赖解析(跨环境引用靠名称重解析,缺失即 failed);历史 payload 快照不保留,版本 +1 只替换 payload。
故事 2 生产环境一键安装:target_ref 回显产物,重名加后缀、下架即拒
背景
生产环境打开商品市场,部署工程师对 sales_amount 商品一键安装。装完后他又装了一次(验证重名策略),再把一个口径有误的对象商品下架(unlisted)并尝试安装——分别观察 target_ref 回显、重名后缀与下架拒绝三条行为。
传统做法对比
以前迁移指标要手工重建实体 / 维度 / 度量 / 指标四层,还要把对象内部 id 对上,错了难发现。现在一键 install 按类型物化:metric 按导出顺序重建四层、object_type_id 经 object_type_name 重解析、JSON 类重名自动加 _imported_<ts> 后缀,安装记录带 target_ref 回显产物引用。
角色
平台管理员 / 部署工程师(安装与下架);商品负责人(维护状态)。
操作步骤
  1. 在商品市场找到 sales_amount 商品,点"安装"
  2. 看返回记录 status 与 target_ref
  3. 再装一次,观察 target_ref 带 _imported_<ts> 后缀、downloads +1
  4. 对另一商品 PUT status=unlisted,再点安装观察拒绝
系统响应
首次安装成功:
POST /api/v1/marketplace/items/5/install
->
{ "code": 0, "data": { "id": 12, "item_id": 5, "version": 1,
    "status": "success", "target_ref": "metric:sales_amount" } }
第二次安装重名后缀:{"target_ref":"metric:sales_amount_imported_<unix 秒>"}。unlisted 商品安装被拒(唯一 4xx):
{ "code": "VALIDATION_ERROR", "error": "unlisted 商品不可安装" }
结果洞察
安装成功 target_ref 回显产物引用、下载计数 +1(安装成功才累加);安装失败返回 status=failed + error(HTTP 恒 200,前端看记录状态而非 HTTP 码);object 类型安装用 ImportObjects,created=0(目标环境已有同名对象)会判安装失败;metric 重名时实体与指标分别独立判重,各自产生后缀副本。
调整建议
安装前先查目标环境同名资源,减少后缀副本;object 商品重复导入看到 failed 要结合 skipped/errors 判断是否"已存在";下架商品仍可看详情与安装记录,重发布会强制置回 published。
动手试一试
登录:admin / admin1。页面路径:商品市场 → 安装。输入内容:对 sales_amount 商品安装两次。预期结果:首次 target_ref=metric:sales_amount;第二次带 _imported_<ts> 后缀;downloads 递增。
限制提示
重发布不自动推送 / 不自动升级已安装环境,需手动重新 install;图表 / 组件对象绑定按导出原值透传,保真度部分;无依赖解析,跨环境引用靠名称在目标环境重解析。
故事 3 把团队 SQL 口径沉淀成宏:一处维护、SQL 工作台与 Notebook 都生效
背景
分析团队多张报表重复写 AND region='华东' 和 AND status IN ('pending','shipped') 的过滤段。张工建一个 macro 仓库"公共口径宏",把这两段定义成宏文件,分析师在 SQL 工作台直接用 {{macro:region_filter}} 引用,改口径只改宏文件。
传统做法对比
以前每个报表各写一遍过滤段,口径一变要全量改、还容易漏;宏展开链路让"一处维护、多处复用"落地。ResolveMacros 挂在 query/translate.go 的 MacroResolver 钩子上,Execute 翻译前自动展开——SqlWorkbench 与 Notebook 的 SQL 单元格共用同一服务实例,两处自动生效,零侵入(resolver 为 nil 时行为不变)。
角色
数据工程师(建仓库 / 定义宏);业务分析师(引用宏写查询)。
操作步骤
  1. POST /api/v1/coderepo/repos 建 macro 仓库"公共口径宏"
  2. POST /coderepo/repos/:id/files 添加 region_filter(内容 AND region = '华东')与 active_status
  3. 在 SQL 工作台写含 {{macro:region_filter}} {{macro:active_status}} 的 SELECT
  4. 用 POST /coderepo/macros/resolve 调试展开结果
系统响应
建仓库与加文件:
POST /api/v1/coderepo/repos { "name": "公共口径宏", "repo_type": "macro" }
-> { "code": 0, "data": { "id": "6a1f...", "name": "公共口径宏",
     "repo_type": "macro", "owner": "admin" } }
POST /api/v1/coderepo/repos/6a1f.../files
{ "path": "region_filter", "content": "AND region = '华东'" }
-> { "code": 0, "data": { "id": "9b2c...", "repo_id": "6a1f...",
     "path": "region_filter", "version": 1 } }
宏展开调试:
POST /api/v1/coderepo/macros/resolve
{ "sql": "SELECT region, SUM(amount) FROM orders WHERE amount > 100 {{macro:region_filter}} {{macro:active_status}} GROUP BY region" }
->
{ "code": 0, "data": { "sql": "SELECT region, SUM(amount) FROM orders WHERE amount > 100 AND region = '华东' AND status IN ('pending','shipped') GROUP BY region" } }
结果洞察
展开规则:宏表优先(只有 repo_type=macro 仓库的文件被命中,template 同名文件不干扰);未命中的 {{macro:name}} 原样保留、不报错(支持"先引用后定义");宏内容可再引用其他宏,迭代展开最多 maxMacroDepth=5 轮(防循环引用死循环);SQL 不含 {{ 时走快速路径零开销。
调整建议
把常用过滤段、日期函数写进 macro 仓库;改口径只改宏文件并覆盖保存(版本归档),全团队 SQL 自动生效;Notebook 的 SQL 单元格与工作台共享宏,无需二次配置。
动手试一试
登录:admin / admin1。页面路径:代码仓库 → 新建 macro 仓库 → 添加 region_filter → 宏展开调试。预期结果:resolve 返回展开后 SQL,{{macro:region_filter}} 被替换为 AND region = '华东'。
限制提示
宏名全局按 path 匹配,无仓库级命名空间;宏展开只在 query.Execute 翻译前(AIP NLQ、报表 SQL 等其他执行入口不消费宏);maxMacroDepth=5,超深嵌套残留原样返回。
故事 4 模板版本覆盖与恢复:fcr_revisions 全程留痕
背景
报表组把月报模板存到 template 仓库 monthly_report.sql,每月迭代覆盖(v1→v2→v3)。某月发现 v2 的指标口径更合理,张工用前端"恢复"把它找回来——他想知道版本历史到底留了什么、恢复后版本号怎么走。
传统做法对比
以前模板改来改去没有版本记录,回退靠记忆和文件备份,谁改了什么都说不清。现在 (repo_id, path) 唯一,新版本覆盖、旧内容归档:归档的是"被顶掉的旧版本",版本历史能还原"谁在何时写了什么",每次覆盖(含恢复)都产生新版本、单调递增、全程留痕。
角色
数据工程师(维护模板与版本历史)。
操作步骤
  1. POST /coderepo/repos 建 template 仓库,POST /repos/:id/files 添加 monthly_report.sql(v1)
  2. PUT /coderepo/files/:id 覆盖为 v2(旧 v1 归档),再覆盖为 v3(v2 归档)
  3. GET /coderepo/files/:id/revisions 查看版本历史
  4. 前端点"恢复"v2(恢复=覆盖写入,产生新版本)
系统响应
覆盖保存 version+1:
PUT /api/v1/coderepo/files/9b2c... { "content": "报告B" }
->
{ "code": 0, "data": { "id": "9b2c...", "path": "monthly_report.sql",
    "version": 2, "updated_by": "alice" } }
版本历史(最新归档在前):
GET /api/v1/coderepo/files/9b2c.../revisions
->
{ "code": 0, "data": { "revisions": [
    { "version": 2, "content": "报告B", "updated_by": "alice", "updated_at": "..." },
    { "version": 1, "content": "报告A", "updated_by": "alice", "updated_at": "..." } ],
  "total": 2 } }
结果洞察
版本模型是"覆盖归档":文件当前 version=N,N-1 及以前的内容归档在 fcr_revisions(version 倒序、最新在前)。恢复历史版本 = 把归档内容作为新版本提交(当前内容先进归档、version+1、内容回到历史值),历史全程保留;content 未变时只改 language/updated_by 不产生新版本。
调整建议
关键模板高频覆盖会累积归档体积,无自动清理策略,建议定期人工归档;并发覆盖无乐观锁(最后一写生效),关键文件人工串行编辑;删除是物理级联,误删无法从平台找回,重要仓库建议备份数据库。
动手试一试
登录:admin / admin1。页面路径:代码仓库 → template 仓库。输入内容:添加 monthly_report.sql,覆盖两次。预期结果:version 递增到 3;revisions 返回 v2、v1 归档;点"恢复"v2 后生成新版本、内容回到 v2。
限制提示
无 git 协议 / 无版本 Diff / 无文件重命名(path 不可改,改名需"新建 + 删除");恢复是覆盖写入而非版本集回退;归档行记录"旧内容 + 旧操作者 + 旧时间",文件行 updated_at 是新时间,两者语义不同。

常见问题

商品市场和本体 YAML 导入导出有什么区别?

本体 YAML 是"单资产、声明式、可进 Git 评审"的通道(/ontology/objects/export|import);Marketplace 是"六类资产、含内部结构、一键安装"的分发通道,带安装记录与下载计数。生产交付建议:Marketplace 商品作分发载体,安装后在本体 YAML 版本治理里做后续演进。

安装失败怎么看?

安装失败不返回 Go error:记录 status=failed + error(HTTP 恒 200),前端需检查记录状态而非 HTTP 码。unlisted 商品安装是唯一返回 4xx(422 VALIDATION_ERROR)的安装前置校验。object 导入 created=0(重复导入)也判 failed。

宏和模板有什么区别?

宏(macro 仓库)是"引用即展开"的内容段:SQL 里的 {{macro:name}} 在执行前被文件内容替换;模板(template 仓库)是"整体复用"的 SQL 片段,人工拷贝参考,不参与自动展开。宏表优先规则保证 template 仓库同名文件不会干扰宏解析。

宏没定义会报错吗?

不会。未命中的 {{macro:name}} 原样保留,SQL 继续走翻译 / 执行,通常由 SQL 语法层暴露问题(残留 {{...}} 不是合法 SQL),适合"先引用后定义"。查库失败(DB 错误)才会诚实报 500。

恢复历史版本会回到旧版本号吗?

不会。版本模型是覆盖归档(append-only 语义):当前内容先进归档、文件 version+1、内容回到历史值。恢复本身也被记录为新版本,版本号单调不减、历史完整保留。

主题小结

一句话:Marketplace 管"资产分发"(六类商品、一键安装、版本 +1、unlisted 拒绝),代码仓库管"口径沉淀"(snippet/template/macro 三类、覆盖归档、宏展开)。记住几个边界:无审核流 / 无依赖解析、重发布不推送、无 git / 无 Diff、宏展开只在 query.Execute 两处生效。