2. FHIR资源与事件建模

在事件驱动的医疗集成中,FHIR 资源既是数据载体,也是事件语义的锚点。理解 Patient、Observation、Encounter 等核心资源的边界,以及它们从创建到作废的生命周期,是设计可靠触发机制的前提。本节从资源建模出发,逐步落到事件触发点识别与版本状态变更的处理方式。

一、核心资源的职责边界

FHIR 用资源(Resource)表达医疗领域中可独立标识、可被引用、可被版本管理的实体或事件记录。集成工程中最常打交道的三类资源各有明确职责:

  • Patient(患者):描述接受医疗服务的个体身份与人口学信息,是绝大多数临床资源的引用主体。关键字段包括 identifier(业务标识,如病案号、医保号)、name、gender、birthDate、active。Patient 本身通常不承载临床事件,而是被其他资源通过 subject 引用。
  • Observation(观察):记录一次测量或断言,如生命体征、实验室结果、评分。核心字段为 status、code(通常用 LOINC 编码)、subject、effectiveDateTime、valueQuantity 或 valueCodeableConcept。Observation 是高频事件源,检验结果回传、监护数据上报都围绕它展开。
  • Encounter(就诊):描述一次医疗服务接触,如门诊、住院、急诊。关键字段包括 status、class(就诊类别)、subject、period、location。Encounter 常作为临床事件的上下文容器,Observation、Condition 等通过 encounter 字段关联到具体就诊。

三者的关系可以概括为:Patient 是被服务对象,Encounter 是服务发生的时空容器,Observation 是容器内产生的具体临床事实。集成设计时,先明确事件属于哪类资源,再决定引用链如何建立。

二、资源生命周期与状态机

FHIR 资源不是静态记录,而是有明确生命周期的对象。以 Observation 为例,其 status 字段定义了从初步到终态的流转:registered(已登记)→ preliminary(初步)→ final(最终)→ amended(已修正)→ corrected(已更正)→ cancelled(已取消)→ entered-in-error(录入错误)。不同状态对应不同的业务含义和下游处理策略。

资源生命周期在集成层面表现为四类操作:create(创建)、update(更新)、delete(删除)、read(读取)。FHIR 的 RESTful 接口用 HTTP 方法映射这些操作:POST 创建、PUT 更新、DELETE 删除、GET 读取。每次写操作都会产生新的版本,资源通过 meta.versionId 和 meta.lastUpdated 记录版本号和最后更新时间。

版本号是乐观锁的基础。更新时若携带 If-Match 头指定版本,服务端可拒绝基于过期版本的覆盖写,避免并发更新丢失。

三、事件触发点识别

事件驱动集成的核心问题是:什么变化值得触发一次消息或工作流?在 FHIR 语境下,触发点通常落在资源状态变更的边界上,而不是每次字段微调。常见的触发点包括:

  • Observation.status 从 preliminary 变为 final:检验结果正式发布,触发结果回传或临床提醒。
  • Encounter.status 从 in-progress 变为 finished:就诊结束,触发费用结算或随访任务。
  • Patient.active 从 true 变为 false:患者记录停用,触发下游系统同步停用。
  • 资源首次创建(create):新患者注册、新医嘱下达,触发通知类事件。

识别触发点时,要区分“数据变更”与“业务事件”。并非所有 update 都是事件,只有状态跨越业务语义边界时才值得触发。实践中常用两种机制捕获触发点:一是订阅(Subscription),服务端在资源满足条件时主动推送;二是轮询加版本比对,客户端定期拉取并按 meta.versionId 判断是否有新版本。

四、版本与状态变更的处理

当同一资源被多次更新,集成系统必须决定如何处理历史版本。FHIR 提供两种视角:当前版本(通过 GET 获取最新)和历史版本(通过 vread 或 _history 获取)。对于事件驱动场景,推荐在消息中携带 meta.versionId 和 meta.lastUpdated,使接收方能够判断事件的新旧与顺序。

状态变更的处理需要关注幂等性。假设一条 Observation 先以 preliminary 推送,随后以 final 推送,接收方若重复收到同版本消息,应能识别并忽略。常见做法是以资源类型 + id + versionId 作为去重键。此外,当资源被标记为 entered-in-error 时,接收方应执行回滚或标记作废,而不是简单覆盖。

示例:Observation 状态变更消息片段
{
  "resourceType": "Observation",
  "id": "obs-10086",
  "meta": {
    "versionId": "3",
    "lastUpdated": "2025-03-12T09:20:00Z"
  },
  "status": "final",
  "code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4" }] },
  "subject": { "reference": "Patient/pat-2001" },
  "encounter": { "reference": "Encounter/enc-3001" },
  "effectiveDateTime": "2025-03-12T09:15:00Z",
  "valueQuantity": { "value": 78, "unit": "次/分" }
}

接收方解析该消息时,先校验 versionId 是否大于本地记录,再根据 status 决定业务动作:final 表示结果可用,entered-in-error 表示需要撤销。若同一 id 出现 versionId 回退,应视为异常并告警。

五、建模实践中的关键判断

在实际项目中,资源建模的难点往往不在字段本身,而在引用关系和事件粒度的选择。例如,一次检验包含多个项目,是生成一个 Observation 还是多个?通常每个可独立解读的结果项对应一个 Observation,通过 panel 或 hasMember 关联。再如,患者信息变更是否触发事件?若变更影响下游用药或计费,应触发;若仅是联系方式微调,可合并到批量同步。

另一个关键点是资源版本与业务版本的区分。FHIR 的 versionId 是技术版本,每次写操作递增;而业务上的“修正”可能对应 status 从 final 到 corrected 的变化。集成设计应同时关注两者:技术版本用于去重和并发控制,业务状态用于决定下游动作。

FHIR 核心资源关系与事件触发链 Patient identifier / name active / birthDate Encounter status / class period / location Observation status / code value / effectiveDateTime subject encounter subject(直接引用) Observation 状态生命周期 registered preliminary final amended cancelled entered- in-error 触发:结果发布 触发:作废回滚 版本与去重处理逻辑 消息携带 versionId + lastUpdated 比对本地版本 versionId 递增? 新版本 处理业务动作 旧版本 忽略 / 告警

上图把三类核心资源的引用关系、Observation 的状态流转以及版本去重逻辑放在同一视图中。可以看到,Patient 通过 subject 被 Encounter 和 Observation 引用,Encounter 又为 Observation 提供就诊上下文;状态机中 final 和 cancelled 是两个关键触发边界;版本比对则决定消息是进入业务处理还是被忽略。

六、落地建议

在具体集成项目中,建议先为每类资源建立一张“状态—触发点—下游动作”对照表,明确哪些状态变化需要推送、推送给谁、接收方如何响应。对于高频资源如 Observation,应优先实现基于 versionId 的幂等处理;对于 Patient 这类主数据,应关注 identifier 的唯一性和合并场景。资源建模不是一次性工作,随着业务规则变化,触发点和状态映射也需要持续维护。

不要把所有 update 都当作事件推送。大量无业务意义的字段更新会造成下游系统重复处理,建议在订阅条件或网关层增加状态过滤,只放行跨越业务边界的变化。