مشروع · 2020 · مؤلف

منصة الوصفات الطبية meinrezept.online

منصة وصفات طبية أونلاين في هامبورغ. قاد backend تطوير microservices بـPHP وNode.js وتوجيه صيدليات بـRabbitMQ تحت حمل زمن الجائحة في 2020.

Screenshot of meinrezept.online - the Hamburg online prescription platform

في أبريل 2020، أصبحت كل شركة ناشئة في مجال الرعاية الصحية في ألمانيا مهمة فجأة.

كانت الصيدليات تعاني الإرهاق. كان الأطباء يُجرون استشارات بالفيديو للمرة الأولى. كان المرضى يُرسلون وصفات ورقية بالبريد. استيقظ البلد بأكمله وأدرك أن مسار الوصفة الطبية - من كتابة الطبيب للوصفة إلى حصول المريض على دوائه - كان شبه تناظري بالكامل. في هامبورغ، انضممت إلى meinrezept.online كأحد قادة الـbackend للمساعدة في بناء النظام الجالس في وسط ذلك.

هذه ليست تدوينة “أنقذنا أرواحاً خلال الجائحة”. إنها تدوينة “هكذا بدت الهندسة فعلاً” - لأن الهندسة كانت مثيرة للاهتمام حقاً ومعظمها غير مرئي من الخارج.

مشكلة صيغة الوصفات الطبية

قبل أن تُوجَّه وصفة إلى صيدلية، عليك قراءتها. في ألمانيا عام 2020، كان هذا يعني التعامل مع صيغ متعددة في آنٍ واحد: نموذج Muster-16 الورقي، وصيغ وصفات رقمية مبكرة لم تكن قياسية بعد، وصور فاكس، وملفات PDF عرضية أنتجتها برامج إدارة عيادات.

لكل صيغة مسار استخراج مختلف. النموذج الورقي له تخطيط معروف لكنه يصل كمسح ضوئي. الصيغ الرقمية لديها بيانات منظَّمة لكن ليس كل الصيدليات تستطيع استهلاك جميعها. ملف PDF من برنامج إدارة العيادة هو الحالة الأسوأ - بيانات منظَّمة محبوسة داخل صيغة وثيقة لم تُعدَّ أصلاً للتحليل البرمجي، بصيغة كانت تتطور بشكل نشط مع دفع الحكومة الألمانية لطرح الوصفة الإلكترونية (eRezept) في الوقت الفعلي.

كانت مهمة الـbackend توحيد كل هذه في تمثيل داخلي واحد يمكن لبقية النظام - طبقة توجيه الصيدليات وخدمة تتبع الطلبات وخدمة إشعار المريض - استهلاكه دون الاهتمام بصيغة الإدخال. هذه مشكلة أصعب مما تبدو حين تتحرك صيغ الإدخال تحتك.

تكامل الصيدليات ثقيل الأنظمة القديمة بالتصميم

هكذا تبدو مساحة تكامل الصيدليات من الـbackend: لكل سلسلة صيدليات كبرى نظام طلباتها الخاص؛ كثير من الصيدليات المستقلة تعمل على WINAPO أو ProFit أو ADG - أنظمة إدارة ممارسة عمرها عقود تكشف واجهات الطلبات عبر بروتوكولات لم تسمع بها في سياق backend حديث. HTTP/JSON ليس الصيغة السلكية المهيمنة. EDIFACT وأصناف HL7 تظهر. بعض الصيدليات لها بوابة ويب وAPI غير رسمية فكّكها شخص ما عكسياً. بعضها لا شيء لديه وتعود إلى إرسال بريد إلكتروني منظَّم.

هذا ليس شكوى. هكذا يبدو تكامل الرعاية الصحية على الطرف المواجه للمستهلك. عمل EHR على نطاق المستشفى الذي أنجزته قبل ذلك (AFAQ، HAKEEM) يعمل على مستوى تجريد مختلف - محركات تكامل مخصصة، وعقود HL7 FHIR، ونظير تقني في الجانب الآخر. على نطاق صيدلية المستهلك، تعمل مع ما تمتلكه الصيدلية فعلاً، وهو كثيراً ما يكون أقل بكثير مما تختار.

RabbitMQ كان وسيط الرسائل، وهو الصحيح تماماً لهذا الحجم. لا حجم مستوى Kafka - تسليم موثوق بين عدد قابل للإدارة من الخدمات مع دلالات إعادة محاولة ومعالجة dead-letter وفصل استقبال الطلبات الواردة عن إرسال الصيدلية الصادر. RabbitMQ يتعامل مع هذا بشكل نظيف. طوبولوجيا التبادل استخدمت تبادلات الموضوع للتوجيه على منطقة الصيدلية ونوع الوصفة ومسار التنفيذ - مما سمح بإضافة منطق توجيه دون لمس كل مستهلك.

Microservices بثلاث لغات

المنظومة كانت PHP 7 وPython وNode.js. هذا هو الواقع. ترث اللغة التي كُتبت بها الخدمة الأولى. تختار الأداة الأفضل للمشكلة التالية - Python لعمل الاستخراج، Node.js لـAPI الحالة real-time. تنتهي بقاعدة كود متعددة اللغات يربطها عقود الرسائل لا اتساق اللغة.

انضباط عقد الرسائل يهم أكثر من اتساق اللغة، بصراحة. إن اتفقت كل خدمة على ما يبدو عليه حدث order.received - المخطط والإصدار والحقول المطلوبة - فإن حقيقة أن المنتج PHP والمستهلك Node.js تفصيل تطبيق. نمط الفشل الخطير هو السماح لكل خدمة بتطوير فهمها للنطاق بشكل مستقل، بحيث يعني order.received شيئاً مختلفاً قليلاً لخدمة إرسال الصيدلية عنه لخدمة إشعار المريض. تكتشف هذا في أسوأ وقت ممكن: حين يتصل مريض بالدعم قائلاً إنه تلقى إشعار “وصفة مُرسَلة” لكن الصيدلية لا سجل لها بالطلب.

ReactJS على الواجهة الأمامية - لم أكن مسؤولاً عنها أساساً - تصل إلى نفس نموذج النطاق عبر API الحالة. الاتساق بين ما يراه المريض في الواجهة وما يقوله سجل أحداث الـbackend هو هاجس تكامل مقلَّل التقدير أصبح مرئياً حين كشف حجم الطلبات في زمن الجائحة عن فجوات توقيت لم تكن بيئات التجهيز قد نمذجتها.

الجدول الزمني للجائحة ضخّم كل شيء

الانضمام في أبريل 2020 يعني الانضمام إلى فريق كان المنتج يُختبر بالحمل من الطلب الحقيقي للمستخدمين بطريقة لم تنمذجها أي بيئة تجهيز. قلق الرعاية الصحية محرّك حركة مرور حقيقي. ارتفاعات حجم الطلبات الواردة تحدث عند أي حدث إخباري - إعلان إغلاق، أو تغيير في ساعات عمل الصيدليات، أو قصة إخبارية عن نقص الأدوية. تتعلم بسرعة كبيرة أي أجزاء خدمتك ذات حالة وأيها لا، لأن الأجزاء ذات الحالة هي التي تفشل بشكل غير رشيق تحت ارتفاعات الحمل.

فصل مسار الاستخراج عن مسار قبول الطلبات كان القرار المعماري الحرج. تكامل صيدلية بطيء لا يجب أن يمنع استقبال وصفة المريض والإقرار بها. الإقرار والتنفيذ هما هاجسان مختلفان ويجب أن يفشلا بشكل مستقل. هذا التغيير أُجري تحت الحمل، وهو ليس مثالياً - لكنه ذاكرة هندسية حقيقية، والمنطق سليم في نظرة إلى الوراء وفي جلسة على لوح رسم.

ما تعلّمك إياه النطاق عبر مستويات الرعاية الصحية

سلّمت برمجيات رعاية صحية على مستويين مختلفَين تماماً: أنظمة EHR وسير عمل سريرية كبيرة على نطاق المؤسسة (نطاق المستشفى ومئات الآلاف من سجلات المرضى ودعم القرار السريري المعقد)، وتطبيقات رعاية صحية مواجهة للمستهلك (طلب وصفات وإشعارات مرضى وتوجيه صيدليات). يشتركان في المتطلبات التنظيمية - معالجة بيانات المريض وصلاحية الوصفة ومسارات التدقيق - لكن تقريباً لا شيء آخر.

الجانب المواجه للمستهلك له دورات تكرار أسرع وعلاقات شراكة تكامل أقل رسمية وضغطاً أكبر للتعامل مع الحالات الطويلة في البيانات. الجانب المؤسسي له تكامل منظَّم وعقود HL7 وFHIR رسمية ودورات تغيير أبطأ. كلاهما صعب. النطاق مفيد.

meinrezept.online لا تزال تعمل. البنية التحتية لتوجيه الصيدليات ومعالجة الوصفات من تلك الفترة - المبنية تحت ضغط غير عادي حقاً، مقابل مشهد تنظيمي متطور، مع فريق يتكيّف مع نفس الإغلاق الذي كان النظام يخدمه - جزء من تلك العملية. الأنابيب لا تزال تجري.