2020年4月、ドイツのすべてのヘルスケアスタートアップが突然重要になった。薬局は圧倒されていた。医師は初めてビデオ診察をしていた。患者は紙の処方箋を郵送していた。国全体が目を覚まし、処方箋パイプライン — 医師がスクリプトを書いてから患者が薬を受け取るまでの経路 — がほぼ完全にアナログだったことに気づいた。ハンブルクで、私はmeinrezept.onlineに参加し、その中間に座るバックエンドの構築を手伝った。
これは「私たちは命を救った」という投稿ではない。「実際のエンジニアリングがどんな様子だったか」という投稿だ。なぜなら、エンジニアリングは本当に面白く、その多くは外からは見えないからだ。
処方箋フォーマット問題
処方箋を薬局にルーティングする前に、それを読まなければならない。2020年のドイツでは、複数のフォーマットを扱うことを意味した:Muster-16の紙のフォーム、まだ標準化されていなかった初期のデジタル処方箋フォーマット、FAX画像、そして医師の診療所が使用しているどんな診療管理ソフトウェアからでも生成されるPDF。
これらそれぞれに異なる抽出パスがある。紙のフォームは既知のレイアウトを持つが、スキャンとして届く。デジタルフォーマットは構造化データを持つが、すべての薬局がそれらをすべて処理できるわけではない。診療管理ソフトウェアからのPDFは最悪のケースだ:プログラム的に解析されることを意図していなかったドキュメントフォーマットの中にロックされた構造化データ。
バックエンドの仕事は、これらすべてを単一の内部表現に正規化することだった。システムの残りの部分 — 薬局ルーティングレイヤー、オーダー追跡サービス、患者通知サービス — が入力フォーマットを気にせずに処理できる表現に。これは入力フォーマットがリアルタイムで進化しているときには驚くほど難しい問題だ。2020年はまさにそうで、ドイツ政府がElektronisches Rezept(eRezept)ロールアウトを積極的に推進していた。
薬局インテグレーションは設計上レガシーが重い
バックエンドから見た薬局インテグレーション空間はこんな様子だ:すべての大手薬局チェーンは独自の発注システムを持ち、多くの独立した薬局はWINAPO、ProFit、ADGを稼働させている — 数十年前からある診療管理システムで、現代のバックエンドコンテキストでは聞いたことのないプロトコル経由で発注インターフェースを公開している。HTTP/JSONはここでは主要なワイヤフォーマットではない。EDIFACTとHL7の変種が現れる。一部の薬局はウェブポータルと誰かがリバースエンジニアリングした非公式のAPIを持つ。何も持たない薬局もあり、構造化されたメールを送るしかない。
これは不満ではない。これはコンシューマー向けエンドでのヘルスケアインテグレーションの実態だ。私が他の場所(AFAQ、HAKEEM)で経験した病院スケールのEHR作業は、別の抽象レイヤーで動作する — HL7 FHIRを扱い、専用のインテグレーションエンジンがあり、薬局や検査室に技術的なカウンターパートがいる。コンシューマー向け薬局アプリスケールでは、薬局が実際に持っているものと一緒に作業することが多く、それは選択するものよりずっと少ないことが多い。
RabbitMQはここでのメッセージブローカーだった。このスケールには理にかなっている。Kafkaレベルのボリュームはやっていない。リトライセマンティクス、デッドレターハンドリング、インバウンドオーダー受信とアウトバウンド薬局ディスパッチを切り離す機能を持つ、管理可能な数のサービス間の信頼性の高い配信をやっていた。RabbitMQはこれをクリーンに処理する。エクスチェンジトポロジー — 薬局のリージョン、処方箋タイプ、フルフィルメントパスに基づいてルーティングするトピックエクスチェンジ — で、すべてのコンシューマーに触れることなくルーティングロジックを追加できた。
三言語と六週間のマイクロサービス
スタックはPHP 7、Python、Node.jsだった。これが現実世界だ。最初のサービスが書かれた言語を引き継ぎ、次の問題に最適なツールを選び(抽出作業にはPython、リアルタイムステータスAPIにはNode.js)、サービス間のメッセージコントラクトによって結び付けられたポリグロットコードベースで終わる。言語の一貫性ではなく。
メッセージコントラクトの規律は言語の一貫性よりも重要だ、正直に言って。すべてのサービスがorder.receivedイベントがどう見えるべきかに合意していれば — スキーマ、バージョニング、必須フィールド — プロデューサーがPHPでコンシューマーがNode.jsであるという事実は実装の詳細だ。危険な失敗モードは、各サービスがドメインの理解を独自に進化させて、order.receivedが薬局ディスパッチサービスと患者通知サービスで微妙に異なる意味になることだ。これは最悪のタイミングで発覚する。患者がサポートに電話して「処方箋が出荷された」という通知を受けたのに薬局はオーダーの記録がないと言っていると言うときだ。
フロントエンドはReactJSで、私が主に担当していたわけではないが、ステータスAPIを通じて同じドメインモデルに接続していた。UIで患者が見るものとバックエンドのイベントログが何を言うかの整合性は、過小評価されたインテグレーションの懸念事項で、パンデミック期のオーダーボリュームがスケールでテストしていなかったタイミングのギャップを明らかにしたとき、非常に目立った。
パンデミックのタイムラインはすべてを増幅した
2020年4月に参加するということは、ステージング環境が想定していなかった方法で製品が実際のユーザー需要によって突然ロードテストされるチームに参加することを意味した。ヘルスケアの不安は本物のトラフィックドライバーだ。インバウンドオーダーボリュームのスパイクはどんなニュースイベントでも起きる — ロックダウン発表、薬局の営業時間の変更、薬不足に関するニュース記事。サービスのどの部分がステートフルでどの部分がそうでないかを非常に素早く学ぶ。負荷スパイク時に非優雅に失敗するのはステートフルな部分だからだ。
これが私に非常に具体的に教えてくれたこと:抽出パイプラインはオーダー受け付けパスから切り離される必要があった。遅い薬局インテグレーションが患者の処方箋の受信と確認を妨げるべきではない。確認とフルフィルメントは別々の懸念事項であり、独立して失敗するべきだ。これを負荷の下で変更した。理想的ではないが、本物のエンジニアリングの記憶だ。
ヘルスケアドメイン全体の幅
私は二つの全く異なるスケールでヘルスケアソフトウェアを出荷してきた:大規模なエンタープライズEHRと臨床ワークフローシステム(病院スケール、数十万の患者記録、複雑な臨床意思決定支援)、そしてコンシューマー向けヘルスケアアプリ(処方箋注文、患者通知、薬局ルーティング)。どちらも規制要件を共有している — 患者データ処理、処方箋の有効性、監査証跡 — しかし他のほぼすべては異なる。
コンシューマー向けサイドは高速な反復サイクル、よりフォーマルでないインテグレーションパートナー関係、そしてデータのエッジケース(変わった処方箋フォーマット、週末に応答しない薬局)のロングテールを処理するより大きなプレッシャーがある。エンタープライズサイドは構造化されたインテグレーション、フォーマルなHL7とFHIRコントラクト、遅い変更サイクルを持つ。どちらも難しい。幅広さは有用だ。
Fulcrumを構築している — ローカルファーストのエージェントコントロールプレーン — 部分的には、分散ヘルスケアバックエンドで学んだコーディネーションパターンがエージェントオーケストレーションに直接適用できるからだ。信頼性の高い配信、デッドレターハンドリング、スキーマバージョン管理されたイベントコントラクト:問題は同じで、ドメインが異なる。