DataHub Service Catalog 编程指南:用 OpenAPI 导入与 SDK 注册构建 repository → service → API 元数据链
【免费下载链接】datahubThe Context Platform for your Data and AI Stack项目地址: https://gitcode.com/GitHub_Trending/da/datahub
本文基于 DataHub 官方教程 service-catalog.md 编写,讲解如何以编程方式填充 DataHub 的 Service Catalog(服务目录):既可以走"铺好的路"——用脚本直接导入一份 OpenAPI 文档,自动创建 Service、逐端点的 API(带类型化签名)以及服务契约;也可以用 SDK 显式注册 REST/GraphQL/gRPC/MCP 服务与代码仓库。读完本文,你能掌握Api助手、ServiceDefinition契约、LargeString大字符串压缩存储,以及 repository → service → endpoint 这条实体链路的完整接线方式。
两种入口与实体链路
DataHub 的 Service Catalog 让软件系统(代码仓库、服务、API、应用)成为与数据资产并列的、带类型、可治理的一等元数据实体。官方功能文档见 Service Catalog 特性指南。本文教程覆盖的是"填充"这一侧,有两条入口:
- 导入 OpenAPI 规范(铺好的路):指向一份 OpenAPI 文档,脚本会创建 Service、为每个端点生成一个带类型化签名的 API,并把完整规范作为服务契约存储。最适合已有 OpenAPI 文档的 REST 服务。
- 显式注册:用 SDK 描述 service、API 和 repository。适用于 gRPC/GraphQL/MCP 服务、源码仓库,或任何没有 OpenAPI 文档的东西。
三类实体构成一条链:repository 构建 service,service 组合其 API(ServiceComposesApi关系),因此图按 repository → service → endpoint 嵌套。这一建模的意义在于:每一跳都是类型化的图边,而不是埋在描述文本里的不透明字符串,从而支持从源码到数据集的影响分析。
前置条件
pip install acryl-datahub datahub init # 写入 ~/.datahubenv,包含你的 GMS URL 与 tokenfrom datahub.ingestion.graph.client import get_default_graph graph = get_default_graph() # 或者设置 DATAHUB_GMS_URL / DATAHUB_GMS_TOKEN 环境变量注意两点实操细节:
- 写入目标是GMS(元数据服务)而非前端端口;示例脚本中统一使用
export DATAHUB_GMS_URL=http://localhost:8080。 - 所有示例都按 URN upsert,脚本是幂等的,可以重复运行。
路径一:导入 OpenAPI 规范(铺好的路)
示例脚本 ingest_openapi_as_service.py 解析 OpenAPI 文档并替你产出完整的 Service 形态:
python metadata-ingestion/examples/services/ingest_openapi_as_service.py \ --spec-url https://api.example.com/openapi.json \ --service-id order-entry-api不带参数运行时,脚本默认抓取DataHub GMS 自身的 OpenAPI 规范——这是快速观察产出形态的便捷方式。从源码可见默认值:规范路径为/openapi/v3/api-docs,默认服务 id 为datahub-gms-api(见 ingest_openapi_as_service.py#L74-L76)。
产出什么
脚本会发出三类元数据:
- 一个Service(子类型
REST_API),在serviceDefinition方面(aspect,format = OPENAPI)上原样携带完整规范。该定义以压缩后的 LargeString存储(大文档做 gzip + base64),避免大型规范撞上 GMS 的 aspect 大小上限——这是 Contract 标签页渲染的事实来源(source of truth),它保留了components、servers以及逐操作签名丢弃的一切内容; - 每个 path + method 一个API(子类型
REST_ENDPOINT),输入/输出签名从操作的 parameters、requestBody 以及200响应 schema 解析而来; - 一条
ServiceProperties.apis边(ServiceComposesApi),把服务与其所有端点关联起来。
命令行参数与环境变量
脚本的参数解析(ingest_openapi_as_service.py#L424-L457)支持以下选项,每个选项都可以用同名环境变量覆盖:
| 参数 | 环境变量 | 说明 |
|---|---|---|
--spec-url | OPENAPI_SPEC_URL | OpenAPI/springdoc 文档 URL;缺省时从--profile的 GMS server + 默认规范路径推导 |
--spec-token | OPENAPI_SPEC_TOKEN | 抓取规范用的 Bearer token;缺省时从 profile 读取 |
--profile | OPENAPI_SPEC_PROFILE | 从~/.datahub/profiles/<profile>读取规范 URL 与 token(token.env中的GMS_API_TOKEN,回退到.datahubenv的token:行) |
--service-id | OPENAPI_SERVICE_ID | 服务 id,生成urn:li:service:<id>,默认datahub-gms-api |
--max-operations | OPENAPI_MAX_OPERATIONS | 最多发出的 api 实体数,默认 40 |
--no-verify-ssl | — | 抓取规范时禁用 TLS 校验 |
端点解析的细节(源码视角)
从 parse_operations() 的实现可以确认几个关键设计决策:
- 逐 path+method 拆分:脚本有意不复用
openapi_parser.get_endpoints,因为后者以 path 为键、每个 path 只保留一个方法,会把同一路径上的 GET/DELETE 静默折叠成一个端点。这里显式遍历spec["paths"][path][method],保证GET /orders/{orderId}与DELETE /orders/{orderId}各自成为独立 API 实体。 - HTTP 方法白名单:只有
get/post/put/patch/delete/head/options这些承载请求/响应语义的方法会被目录化。 - $ref 解析:复用 openapi_parser 的
get_schema_from_response/resolve_schema_references处理引用展开。 - 类型提示保留嵌套:嵌套结构不展开成点号参数,而是用
data_type提示表达,例如array<Order>、object(见_type_hint(),#L237-L248)。 - JSON media type 的宽容匹配:springdoc 会输出
application/json;charset=utf-8这类带 charset 后缀的内容类型,脚本按前缀匹配而非精确匹配,避免漏取。
大规范的截断策略
完整规范始终全量落在serviceDefinition上(压缩 LargeString,无损);受限的是逐操作的 API 实体——MAX_OPERATIONS默认为 40(#L68-L71),防止 1000+ 端点的规范把图冲垮。当发生截断时,build_service()会按"签名丰富度降序"确定性挑选保留哪些端点,优先保留真正携带类型化签名(params/returns)的操作,并在日志中告警列出被跳过的操作名(#L347-L371)。
main()的写入顺序也值得注意:先api.emit(graph)拿到全部 API 的 URN,再重新打戳ServiceProperties(带上真实 api URN 列表)发出ServiceComposesApi边,然后才发出其余 service aspects(#L482-L503)——因为apis字段必须在 API 实体存在后填充。
路径二:显式注册
注册一个端点(API)
一个API(urn:li:api:<id>)是带类型化签名的命名可调用单元。Api助手同时也是 Agent Registry 中注册工具时用的同一个助手,完整参考见 Agent Registry 教程。简版示例:
from datahub.api.entities.agent.api import Api, ApiParam, API_SUBTYPE_REST_ENDPOINT place_order = Api( id="order-entry-api./orders.POST", name="POST /orders", subtypes=[API_SUBTYPE_REST_ENDPOINT], description="Place a trade order.", parameters=[ApiParam(name="body", data_type="object", required=True)], returns=[ApiParam(name="order", data_type="object")], method="POST", path="/orders", ) api_urn = place_order.emit(graph)深入 Api 助手源码 可以确认它的行为边界:
- 合法子类型:
MCP_TOOL、REST_ENDPOINT、GRPC_METHOD、GRAPHQL_OPERATION、FUNCTION(#L33-L45),非法值会在 pydantic 校验时直接抛错。"是某个 agent 的工具"是一种关系而非固有类型——MCP tool 本质上就是一个 API。 - REST 规范 id:
Api.rest_id(service_id, method, path)生成<service_id>/<METHOD>/<path>的确定性 id(#L199-L220)。无论手写脚本还是 OpenAPI 导入器注册同一端点,都会解析到同一个 URN;path 原样保留(斜杠与{param}花括号不转义),因此/orders/{orderId}与字面/orders/orderId映射到不同 id,不会碰撞。 - 类型映射:
ApiParam.data_type是友好类型提示,string/number/integer/float/boolean/object/array/date/datetime会映射到对应的SchemaField类型成员,原始字符串保留在nativeDataType上,所以array<Order>这类富提示不丢失(#L61-L87)。未知类型回退为 StringType,不报错。 - emit 的 aspect 序列:
generate_mcp()依次产出apiProperties(名称/描述/外链/审计戳)、apiSignature(输入/输出 SchemaField,独立 aspect 以便契约变更时单独重灌)、REST 端点的restApiProperties(method+path)、subTypes、可选的dataPlatformInstance,以及status(removed=False)(#L231-L286)。emit(graph)返回该 API 的 URN,供后续ServiceProperties.apis引用。 - method 校验:HTTP method 被规范化为大写并校验,合法集合为
GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS/TRACE。
注册一个服务(Service)
Service 承载身份与契约,但没有 schema/列,所以以 aspects 形式发出。可运行示例:register_rest_services.py 注册了一个完整的 "Order Entry API" 演示服务(含一份内嵌的 OpenAPI 3.1 YAML 契约和三个 REST_ENDPOINT API)。核心骨架如下:
from datahub.api.entities.common.large_string import make_large_string from datahub.emitter.mce_builder import make_data_platform_urn from datahub.emitter.mcp import MetadataChangeProposalWrapper from datahub.metadata.schema_classes import ( DataPlatformInstanceClass, ServiceDefinitionClass, ServiceDefinitionFormatClass, ServiceLifecycleClass, ServicePropertiesClass, StatusClass, SubTypesClass, ) service_urn = "urn:li:service:order-entry-api" aspects = [ ServicePropertiesClass( displayName="Order Entry API", description="Place, look up, and cancel trade orders.", apis=[api_urn], # ServiceComposesApi 边 lifecycle=ServiceLifecycleClass.PRODUCTION, ), ServiceDefinitionClass( # 契约,渲染在 Contract 标签页 format=ServiceDefinitionFormatClass.OPENAPI, rawSpec=make_large_string(open_api_yaml), version="1.4.0", externalUrl="https://api.example.com/docs", ), SubTypesClass(typeNames=["REST_API"]), # 或 GRAPHQL、GRPC、MCP DataPlatformInstanceClass(platform=make_data_platform_urn("openapi")), StatusClass(removed=False), ] for aspect in aspects: graph.emit_mcp(MetadataChangeProposalWrapper(entityUrn=service_urn, aspect=aspect))一个容易踩坑的细节,示例脚本 register_rest_services.py#L238-L251 处理得很典型:ServiceProperties是整方面覆盖式的,重跑脚本时若直接覆盖,会抹掉之前由 register_repositories.py 写入的sourceRepository字段。正确做法是先graph.get_aspect(urn, ServicePropertiesClass)读出旧值,把sourceRepository带回新 aspect 再发出——即"本次写入只拥有 displayName/description/apis/lifecycle,不置空自己没设置的字段"。
make_large_string来自 large_string.py:小字符串直接存储,大文档自动 gzip + base64,配合uncompressedSize字段让 GMS 侧可以透明解压——这就是多 MB 的 OpenAPI 规范能塞进单个 aspect 的机制。
MCP 服务器
MCP 服务器是子类型为MCP的 Service:它的工具是Api实体(子类型MCP_TOOL),经ServiceProperties.apis关联;其serviceDefinition存放tools/list契约(JSON Schema 格式)。连接细节(transport、URL、headers、timeout)放在McpServerPropertiesaspect 上。完整模式见 register_mcp_servers.py。
从源码看一个有意思的建模决策:MCP 服务不使用OpenAPI 文档作为契约,因为 MCP 协议本身不说 OpenAPI——它构造的契约是tools/listJSON-RPC 载荷:每个工具带inputSchema与outputSchema(JSON Schema),format为JSON_SCHEMA,version标注所遵循的 MCP 协议修订(见 build_mcp_tools_list_contract())。该示例以 DataHub 自身的 MCP server(@acryldata/mcp-server-datahub,暴露 search/lineage/metadata 工具)作为主示例。
GraphQL 与 gRPC 服务
register_graphql_grpc_services.py 证明了 Service/api/contract 模型是协议无关的:同一套类型化形态覆盖 REST、GraphQL 与 gRPC。
- GraphQL:契约为 SDL(
ServiceDefinitionFormatClass.GRAPHQL_SDL),每个 operation 是一个Api,子类型GRAPHQL_OPERATION,id 约定形如<service_id>.query.orders,参数类型直接用 GraphQL 标量(ID、Int、Float、String等); - gRPC:契约为 protobuf 文本(
ServiceDefinitionFormatClass.GRPC_PROTO),每个 rpc 方法是一个Api,子类型GRPC_METHOD,id 约定形如<service_id>.PricingService.GetPrice。
两者的 Service 注册流程与 REST 完全一致:先发 Api 拿 URN,再以 aspect 列表(ServiceProperties/ServiceDefinition/SubTypes/DataPlatformInstance/Status)发出 Service。
应用与服务健康
关于 Application 分组与服务健康检查(incident 联动)的注册模式,可参考 register_app_and_health.py 以及 Applications 教程。
注册代码仓库(Repository)
repository(urn:li:repository:<id>)是这条链的起源节点(genesis node)——它产出服务。注册方式是先写入仓库自身的 properties 与 source,再把SourcedFrom边接到它所构建的服务上(通过设置该服务的sourceRepository字段):
from datahub.metadata.schema_classes import ( RepositoryPropertiesClass, RepositorySourceClass, ) repo_urn = "urn:li:repository:acme.payments" for aspect in [ RepositoryPropertiesClass( name="payments", description="Payments platform service repository.", defaultBranch="main", languages=["Java", "Kotlin"], license="Apache-2.0", homepageUrl="https://github.com/acme/payments", archived=False, ), RepositorySourceClass( externalUrl="https://github.com/acme/payments", externalId="acme/payments", ), SubTypesClass(typeNames=["GIT_REPOSITORY"]), DataPlatformInstanceClass(platform=make_data_platform_urn("github")), StatusClass(removed=False), ]: graph.emit_mcp(MetadataChangeProposalWrapper(entityUrn=repo_urn, aspect=aspect)) # 接线 repo -> service,同时不破坏服务的其他字段: service_props = graph.get_aspect(service_urn, ServicePropertiesClass) service_props.sourceRepository = repo_urn graph.emit_mcp(MetadataChangeProposalWrapper(entityUrn=service_urn, aspect=service_props))可运行示例:register_repositories.py。从 wire_sourced_from() 的实现可以确认两个要点:
- 先读后写:先
get_aspect取回服务现有的ServiceProperties,在其上追加sourceRepository再重发,绝不覆盖 displayName/apis 等已有字段;若服务尚不存在(比如还没跑过register_rest_services.py),脚本会告警并跳过接线,但仓库本身仍会被注册。 - 链路的嵌套原则:端点不直接从 repository 连出——它们通过服务上的
ServiceComposesApi边挂在 service 下,因此链路嵌套为 repository → service → endpoint,而不是仓库向服务和所有端点并行扇出。Repository 的 profile 上,入向SourcedFrom边展示为"这个仓库产出了什么"。
另一个从源码可见的约定:repository 的 id 按<platform>.<org>/<name>构造(如github.acme/payments),平台信息放在dataPlatformInstanceaspect 而非 URN 键中,保持平台无关性(见 register_repositories.py#L9-L11、L52-L60)。
验证与后续查看
所有写入都按 URN upsert,重复运行示例脚本是安全的。注册完成后可在前端查看实体 profile(以默认本地部署为例):
- 服务:
http://localhost:9002/service/urn:li:service:order-entry-api(Contract 标签页渲染 OpenAPI 文档) - 仓库:
http://localhost:9002/repository/urn:li:repository:github.acme/payments
相关资源
- Service Catalog 特性指南:概念总览、发现/影响分析/契约/健康等前端视角,以及 Service(目录实体)与 DataHub 自身 MCP Server 的区别
- Agent Registry 教程:
Api助手的完整参考,以及调用这些 API 的 AI agent - Applications 教程:按业务目的分组资产
- 核心源码:Api 助手、LargeString 工具、OpenAPI 解析器
【免费下载链接】datahubThe Context Platform for your Data and AI Stack项目地址: https://gitcode.com/GitHub_Trending/da/datahub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考