3. FHIR订阅机制详解
FHIR 的订阅机制解决的是一个非常具体的问题:当某个临床事件发生后,如何让关心它的系统在不需要反复轮询的情况下及时获知。FHIR R4 用 Subscription 资源来描述“谁在什么条件下、通过什么通道、接收什么通知”。理解订阅,核心就是理解三件事:criteria 决定“什么事件算数”,channel 决定“通知怎么送”,status 决定“这条订阅当前是否生效”。
Subscription 资源结构
一个典型的 R4 Subscription 资源包含以下关键字段:
status:订阅的生命周期状态,取值 requested、active、error、off、entered-in-error。reason:人类可读的订阅原因,便于运维排查。criteria:订阅的过滤条件,通常是一个带查询参数的 FHIR 搜索 URL,例如Observation?code=http://loinc.org|8867-4&status=final。channel:通知通道,包含type、endpoint、payload、header等子字段。end:订阅的过期时间,可选。
其中 channel.type 在 R4 中支持 rest-hook、websocket、email、sms、message 等。工程上最常用的是 rest-hook 和 websocket。rest-hook 表示服务端在事件触发时向订阅方提供的 HTTP(S) 端点发起 POST 请求;websocket 表示服务端与订阅方维持一条 WebSocket 长连接,事件发生时通过该连接推送消息。
criteria:把“事件”翻译成搜索条件
criteria 的本质是一个 FHIR 搜索表达式。服务端在资源发生 create、update 或 delete 时,会用该表达式去匹配变更后的资源。例如订阅“所有新产生的最终状态心率观察”,criteria 可写为:
Observation?code=http://loinc.org|8867-4&status=final
需要注意几个工程细节。第一,criteria 中的搜索参数必须被服务端支持,否则订阅可能进入 error 状态。第二,criteria 通常只对“资源级”变更生效,若需要更细粒度的条件(如仅当 valueQuantity 超过阈值),R4 的 criteria 表达能力有限,往往需要服务端扩展或改用 R5 的 SubscriptionTopic。第三,criteria 的匹配发生在服务端,订阅方无法在通道层再做过滤,因此条件应尽量精确,避免推送风暴。
channel:rest-hook 与 WebSocket 的差异
rest-hook 的工作方式是:服务端在事件触发时,向 channel.endpoint 指定的 URL 发送一个 HTTP POST,请求体通常是 Bundle(type=history 或 notification),其中包含触发事件的资源或仅包含通知头。订阅方必须返回 2xx,否则服务端可能重试或把订阅置为 error。rest-hook 的优点是实现简单、跨网络友好、易于与现有 Web 服务集成;缺点是每次事件都要建立 HTTP 连接,高并发下开销较大,且需要订阅方暴露可被服务端访问的端点。
WebSocket 的工作方式是:订阅方先与服务端建立 WebSocket 连接,服务端在事件发生时通过该连接推送消息。它的优点是长连接、低延迟、服务端可主动推送;缺点是连接需要维护、断线重连逻辑复杂、部分网络环境对 WebSocket 支持不佳。选择时通常遵循:低频、跨组织、需要审计的场景优先 rest-hook;高频、同一信任域、需要低延迟的场景可考虑 WebSocket。
订阅状态管理
Subscription.status 不是装饰字段,而是控制订阅是否真正生效的开关。典型生命周期如下:
- requested:订阅方创建 Subscription 后,服务端尚未确认或尚未激活。
- active:服务端已接受并开始按 criteria 推送。
- error:推送失败、endpoint 不可达或 criteria 无法解析,服务端标记异常。
- off:订阅被主动关闭或已过期。
- entered-in-error:订阅本身录入错误,不应再使用。
工程上要特别关注 error 状态。rest-hook 场景下,如果订阅方端点连续返回 5xx 或超时,服务端可能重试若干次后把订阅置为 error。此时订阅方需要主动查询 Subscription 资源,确认状态并重新激活。WebSocket 场景下,连接断开后服务端可能无法推送,订阅方应实现指数退避重连,并在重连成功后检查订阅是否仍为 active。
一个可落地的订阅配置示例
假设我们需要订阅“最终状态的心率 Observation”,并通过 rest-hook 推送到内部事件网关。可构造如下 Subscription:
{
"resourceType": "Subscription",
"status": "requested",
"reason": "心率最终结果实时入湖",
"criteria": "Observation?code=http://loinc.org|8867-4&status=final",
"channel": {
"type": "rest-hook",
"endpoint": "https://gateway.example.org/fhir/notify",
"payload": "application/fhir+json",
"header": ["Authorization: Bearer <token>"]
},
"end": "2025-12-31T23:59:59Z"
}
服务端接受后会把 status 置为 active。当一条 status=final 且 code=8867-4 的 Observation 被创建或更新时,服务端向 endpoint 发送 POST,请求体为包含该 Observation 的 Bundle。订阅方收到后应先校验来源,再按 Observation.id 做幂等处理,避免重复入库。
status 或 code 的联合过滤,订阅可能无法激活或频繁进入 error。上线前应先用普通搜索请求验证 criteria 的可执行性。选型与运维要点
rest-hook 与 WebSocket 并非互斥,很多集成架构会同时使用:低频、跨组织的订阅走 rest-hook,院内高频、低延迟的订阅走 WebSocket。无论哪种通道,都应把 Subscription 当作一等资源来管理:记录其 id、criteria、channel.endpoint、status 和 end,纳入监控与告警。对于 rest-hook,端点必须具备快速返回 2xx 的能力,重业务逻辑应异步化;对于 WebSocket,必须实现心跳、断线重连和消息去重。只有把 criteria 的精确性、channel 的可靠性和 status 的可观测性三者结合起来,FHIR 订阅才能在真实医疗集成中稳定运行。