项目 · 2020 · 作者

meinrezept.online 处方平台

汉堡在线处方平台。后端负责人用 PHP/Node.js microservices 和 RabbitMQ 药店路由,在 2020 年疫情压力测试下构建了真实世界的处方处理链路--当整个德国同时意识到处方流程几乎完全是模拟的时候。

Screenshot of meinrezept.online - the Hamburg online prescription platform

2020 年 4 月,德国每一家医疗科技初创公司突然变得重要起来。

药店不堪重负,医生第一次做视频问诊,患者把纸质处方邮寄过来。整个国家醒过来,注意到处方流水线—从医生开单到患者拿到药这段路—几乎完全是模拟的。在汉堡,我以后端负责人之一的身份加入了 meinrezept.online,帮助构建坐在中间的系统。

这不是一篇「我们在疫情中救了生命」的帖子,而是一篇「工程实际上是什么样的」的帖子—因为工程是真正有意思的,而且大部分从外部看是不可见的。

处方格式问题

在你能把处方路由到药店之前,你必须先读懂它。2020 年德国这意味着同时处理多种格式:Muster-16 纸质表格、尚未标准化的早期数字处方格式、传真图片,以及偶尔出现的医生办公室从任何他们在用的诊所管理软件生成的 PDF。

每种格式都有不同的提取路径。纸质表格有已知的布局,但以扫描件形式到来。数字格式有结构化数据,但不是所有药店都能消费所有格式。来自诊所管理软件的 PDF 是最糟糕的情况—结构化数据锁在一个从来不是被设计为程序化解析的文件格式里,而且格式在德国政府实时推进 Elektronisches Rezept(eRezept)推广的过程中持续演化。

后端的工作是把所有这些归一化为单一的内部表示,让系统的其余部分—药店路由层、订单追踪服务、患者通知服务—能消费,而不需要关心输入格式。当输入格式在你脚下移动时,这比听起来难。

药店集成天然是重遗留的

从后端看药店集成空间是什么样的:每家大型连锁药店都有自己的订购系统;许多独立药店跑 WINAPO、ProFit 或 ADG—几十年历史的诊所管理系统,通过你在现代后端上下文里没听说过的协议暴露订购接口。HTTP/JSON 不是主导线格式,EDIFACT 和 HL7 变体会出现。有些药店有 Web 门户和某人逆向工程出的非官方 API,有些什么都没有,你就得回去发结构化邮件。

这不是抱怨,这就是医疗集成在消费端的样子。我之前做的医院级 EHR 工作(AFAQ、HAKEEM)在不同的抽象层级运作—专用集成引擎、HL7 FHIR 合同、另一端系统的技术对口人。在消费药店规模,你和药店实际有的东西一起工作,这通常比你会选择的少得多。

RabbitMQ 是消息 broker,对这个规模来说正好。不是 Kafka 级别的吞吐量—而是可管理数量服务之间的可靠传输,有重试语义、死信处理,以及入站订单接收和出站药店 dispatch 的解耦。RabbitMQ 干净地处理这些。Exchange 拓扑使用了按药店地区、处方类型和履行路径路由的 topic exchange—让我们能在不碰每个消费者的情况下添加路由逻辑。

三种语言的 Microservices

技术栈是 PHP 7、Python 和 Node.js。这就是真实世界,你继承第一个服务所用的语言,为下一个问题选最好的工具—Python 做提取工作,Node.js 做实时状态 API—然后得到一个靠消息合同而不是语言一致性维系在一起的多语言代码库。

老实说,消息合同的纪律比语言一致性更重要。如果每个服务都对 order.received 事件是什么样的达成一致—schema、版本控制、必填字段—那么生产者是 PHP 而消费者是 Node.js 只是实现细节。危险的故障模式是让每个服务独立演化对领域的理解,使得 order.received 对药店 dispatch 服务意味着的东西和对患者通知服务意味着的稍有不同。你在最糟糕的时候发现这个:当一个患者打来客服电话说他们收到了「处方已发出」通知但药店没有任何订单记录。

前端的 ReactJS—主要不是我负责—通过状态 API 连接到同一个领域模型。患者在 UI 里看到的和后端事件日志里的东西之间的一致性,是一个被低估的集成关切,在疫情时期的订单量暴露了测试环境没有预料到的时序间隙时变得可见。

疫情时间线放大了一切

4 月加入意味着加入了一个产品正在被真实用户需求压力测试的团队,而那种方式是任何测试环境都没有建模的。医疗焦虑是真实的流量驱动因素,入站订单量在任何新闻事件周围都会飙升—封锁公告、药店营业时间变化、关于药品短缺的新闻报道。你非常快地学到了你的服务哪些部分是有状态的、哪些不是,因为有状态的部分是在负载峰值下非优雅失败的那些。

提取链路与订单接收路径解耦是关键架构决策。慢的药店集成不应该阻止患者的处方被接收和确认。确认和履行是不同的关切,应该独立失败。这个改变是在负载下做出的,这不是理想情况—但这是一段真实的工程记忆,回头看和在白板上看,推理都是合理的。

跨医疗规模的范围教给你什么

我在两个截然不同的规模上交付过医疗软件:大型企业 EHR 和临床工作流系统(医院规模,数十万患者记录,复杂的临床决策支持),以及面向消费者的医疗 app(处方订购、患者通知、药店路由)。它们共享监管要求—患者数据处理、处方有效性、审计轨迹—但几乎没有其他共同点。

消费端有更快的迭代周期、不那么正式的集成合作关系,以及处理数据长尾边缘情况更大的压力。企业端有结构化集成、正式的 HL7 和 FHIR 合同,以及更慢的变更周期。两者都难,这个范围是有用的。

meinrezept.online 还在运营。这段时期的药店路由和处方处理基础设施—在真正不寻常的压力下、在演化中的监管格局下、由一个在适应同样封锁的团队建造—是那次运营的一部分。链路还在跑。