منصة لتنسيق العمل داخل المستشفى أعمل على بنائها حاليًا: ملفات المرضى، والمواعيد، والاستشارات السريرية، ونتائج المختبر، والوصفات الطبية، والوثائق، ومساعد بالذكاء الاصطناعي، صُممت كخدمات مستقلة تبقى متزامنة بالإعلان عمّا حدث بدل أن يسأل بعضها بعضًا. اكتمل التصميم عبر المخططات الاثني عشر أدناه، والبناء جارٍ: خدمات المرضى والمواعيد والبوابة تعمل بالفعل، والخدمة السريرية هي التالية.
المشكلة الأساسية
معلومات المريض موزعة على أجزاء. مواعيده في نظام، وتحاليله في آخر، ووصفاته في ثالث، وفواتيره في رابع. ولا يتواصل أي منها مع الآخر كما ينبغي.
والعواقب عادية ومتكررة. يصف طبيب دواءً دون أن يرى أنه يتعارض مع دواء وصفه طبيب آخر الشهر الماضي. تبقى نتيجة حرجة دون أن يقرأها أحد لساعات. يتصل مريض بالاستقبال ليسأل عن أمر تجيب عنه وثيقة لا يجدها أحد. ويعيد الموظفون كتابة المعلومات نفسها في أربع شاشات مختلفة.
لا شيء من هذا مشكلة تقنية بالمعنى المثير. إنها مشكلة تنسيق. المعلومة موجودة، لكنها لا تصل أبدًا إلى حيث يُحتاج إليها، في الوقت الذي يُحتاج إليها فيه.
الحل
صُمم HealthPack من خمس عشرة خدمة صغيرة مستقلة بدل خدمة واحدة ضخمة. تتولى كل منها مهمة واحدة وتتقنها: واحدة لملفات المرضى، وواحدة للمواعيد، وواحدة لنتائج المختبر، وواحدة للوصفات، وواحدة للوثائق، وواحدة للمساعد الذكي.
وتبقى متزامنة بأن يبث بعضها لبعض ما يحدث. حين تُعتمد نتيجة مخبرية، تعلن خدمة المختبر ذلك مرة واحدة على lab.result.finalized. وكل ما يعنيه الأمر — السجل الطبي الزمني للمريض، ونظام تنبيهات الطبيب، ومسار الفوترة، وفهرس البحث الخاص بالمساعد الذكي — يتلقى الإعلان نفسه عبر Kafka ويتفاعل معه باستقلالية. لا أحد مضطر لأن يتذكر إبلاغ أحد.
هذه هي الفكرة كلها: النظام يخبر نفسه بما حدث، والأمور الصحيحة تحدث تلقائيًا. ينقل Kafka الوقائع — أشياء حدثت، قابلة لإعادة التشغيل، ومحفوظة — وينقل RabbitMQ المهام — أنشئ هذا الملف PDF، أرسل هذا الإشعار — عامل واحد، ومحاولة واحدة، تُعاد عند الفشل.
الأجزاء الصعبة
الأعطال تحدث، وعلى النظام أن يصمد أمامها. في نظام من خمس عشرة خدمة مستقلة، توقف إحداها مؤقتًا أمر عادي لا استثنائي. ويحصر تصميم HealthPack العطل في مكانه: إذا توقفت خدمة الإشعارات، تستمر حجوزات المواعيد، وتنتظر التذكيرات في RabbitMQ ثم تُرسل حين تعود الخدمة. لا شيء يضيع.
لا ينبغي أن يحدث شيء مرتين. يجب ألا تصدر وصفة طبية مرتين لأن طلبًا عبر الشبكة أُعيدت محاولته. كل عملية إنشاء وثيقة تحمل مفتاح عدم تكرار يُتحقق منه قبل بدء أي عمل، بحيث تعطي إعادة الأمر نفسه النتيجة ذاتها التي يعطيها تنفيذه مرة واحدة.
السرعة حيث تهم. تجمع لوحة تحكم الطبيب معلومات من خمس خدمات مختلفة. لو بُنيت بسذاجة لكانت عشرات الطلبات المنفصلة وصفحة بطيئة. تجمعها طبقة GraphQL في استدعاء gRPC واحد لكل خدمة بدل استدعاء لكل صف مريض، فتُحمَّل الصفحة في رحلة ذهاب وإياب واحدة.
الخصوصية قيد في التصميم لا ميزة إضافية. يُتحقق من الموافقة قبل أن يقرأ المساعد الذكي أي شيء. وتُحذف البيانات الشخصية قبل أن تغادر أي معلومة خوادم المستشفى نفسه. ليست هذه إضافات — فالبنية تفترضها منذ المخطط الأول.
ملفات المرضى
هوية واحدة موثقة لكل مريض، حتى تتحدث كل الخدمات الأخرى عن الشخص نفسه. تُشفَّر الحقول الحساسة كلٌّ على حدة، لا مجرد القول إن «قاعدة البيانات مشفرة».
المواعيد
يحجز المرضى عبر الإنترنت ويرون التوفر الحقيقي. ويجعل قيد استبعاد في قاعدة البيانات حفظ حجزين للطبيب نفسه في الفترة نفسها مستحيلًا بنيويًا، لا مجرد أمر يُتحقق منه بعناية.
السجلات السريرية
يسجل الأطباء الزيارات والتشخيصات والقياسات. كل شيء مؤرخ ومربوط بالسجل الزمني للمريض ومرمَّز وفق ICD-10 وSNOMED CT وLOINC، وهي المعايير التي تستعملها المستشفيات فعلًا.
نتائج المختبر
تصل النتائج بصيغة المراسلة الاستشفائية القياسية (HL7 v2) مباشرة من أجهزة المختبر. وتراقب عملية بث مستمر كل نتيجة بحثًا عن القيم الخطيرة وتنبه الطبيب في ثوانٍ، متجاوزة وضع عدم الإزعاج.
الوصفات الطبية
قبل إصدار أي وصفة، تُقارن بكل ما يتناوله المريض حاليًا. التداخل الدوائي الخطير يوقف الطلب ويبيّن السبب بدقة؛ وأي تجاوز يُسجَّل بشكل دائم مع السبب الذي يقدمه الطبيب.
الوثائق
تُنشأ تقارير الخروج وتقارير المختبر والوصفات والفواتير بصيغة PDF صالحة للأرشفة، موقعة تشفيريًا بحيث يُكشف أي تلاعب. ويجري الإنشاء في الخلفية، فلا أحد ينتظر أمام شاشة تحميل.
المساعد الذكي
يطرح المرضى والطاقم أسئلتهم بلغة عادية. ولا يجيب المساعد إلا انطلاقًا من الوثائق الفعلية لذلك المريض، ويذكر مصدر كل معلومة، ويصرّح بوضوح حين لا تتضمن الوثائق جوابًا.
المراسلة
محادثة فورية بين المرضى والأطباء، ومع المساعد الذكي. تصل الإجابات رمزًا برمز بدل أن تظهر بعد انتظار طويل.
الإشعارات
تذكيرات بالمواعيد، وتنبيهات بجاهزية النتائج، وقيم حرجة، تُرسل عبر إشعارات الهاتف أو البريد الإلكتروني أو الرسائل القصيرة حسب ما اختاره كل شخص. وتسلك التنبيهات الحرجة مسارًا منفصلًا وأسرع يتجاوز ساعات الهدوء.
الفوترة
تُنشأ الفواتير تلقائيًا عند انتهاء الزيارة، مع معالجة الدفع بالبطاقة عبر Stripe، ونمط outbox معاملاتي يُبقي الطرفين متزامنين.
سجل التدقيق
كل إجراء يقوم به أي شخص يُسجَّل في سجل مسلسل بالتجزئة يكشف أي تلاعب. تعديل أي قيد سابق يكسر السلسلة ويُكتشف — وهذا ما تفرضه القوانين، وتطبقه أغلب الأنظمة بشكل سيئ.
تعمّق تقني
برمجيات الرعاية الصحية مشكلة أنظمة موزعة صعبة حقًا ترتدي زيًّا مملًّا. فيها متطلبات اتساق صارمة (لا يمكنك حجز غرفة عمليات مرتين)، ومتطلبات زمن استجابة صارمة (قيمة بوتاسيوم حرجة لا قيمة لها بعد ساعة من التأخير)، ومتطلبات امتثال صارمة (كل قراءة لملف مريض قابلة للتدقيق بحكم القانون)، وواجهة تكامل متباينة إلى أقصى حد (HL7 v2 من عام 1989 بجوار FHIR REST)، وبيانات صحية شخصية تجعل لكل قرار يخص تدفق البيانات عواقبه.
اخترته عن قصد. كان عملي السابق — بوابة Drupal/Next.js في Atos، ومنصة توصيل بـ Spring Boot في OpenTecc — متقن البناء لكنه أحادي البنية. كانت «الخدمات المصغرة» و«Kafka» مفاهيم أفهمها، لا منجزات أستطيع الإشارة إليها. وُجد هذا المشروع ليجعل هذا الادعاء قابلًا للإثبات، ولبناء شيء يمكن أن يساعد مستشفى فعلًا على العمل بأمان أكبر، لا لمجرد تضخيم قائمة التقنيات.
لماذا لن ينجح النظام الأحادي
أربع خصائص في هذا المجال تفرض التقسيم:
1حِمل غير متكافئ إلى حد كبير
تعمل استعلامات المصطلحات في كل كتابة سريرية تقريبًا، بينما تعمل الفوترة مرة واحدة لكل لقاء. في النظام الأحادي تتقاسمان مجموعة الخيوط والذاكرة نفسها؛ فتضعف حلقة مصطلحات مكثفة عملية إنشاء الفواتير دون سبب.
2ميزانيات زمن استجابة غير متكافئة إلى حد كبير
لتنبيه القيمة الحرجة اتفاقية مستوى خدمة بالثواني. أما إنشاء ملف PDF فقد يستغرق ثلاثين ثانية دون أن يهتم أحد. ووضعهما في مسار الطلب نفسه يعني أن الميزانية الأشد صرامة تفرض نفسها في كل مكان.
3مجالات أعطال مستقلة
يجب ألا يمنع توقف Stripe طبيبًا من تسجيل لقاء. في النظام الأحادي، يجعل نطاق المعاملات المشترك ومجموعات الاتصالات المشتركة تحقيق هذا العزل صعبًا جدًا في الواقع.
4نطاق الأثر التنظيمي
الخدمات التي تلمس البيانات الصحية الشخصية تحتاج إلى التشفير وبوابات الموافقة والتدقيق. والخدمات التي لا تلمسها لا ينبغي أن تدفع هذه الضريبة. أما النظام الأحادي فيُخضع كل مكوّن لهذه القيود افتراضيًا.
استراتيجية البروتوكولات
ثلاث وسائل نقل، لكل منها مبرر في جملة واحدة.
REST — كل واجهة برمجية خارجية أو عابرة للحدود. قابلة للتخزين المؤقت، ويمكن تتبعها بـ curl، ومدعومة أصلًا في المتصفح، وهي الخيار الافتراضي الصحيح. و FHIR R4 نفسه مبني على شكل REST، فتأتي قابلية التشغيل البيني في المجال الصحي مجانًا.
GraphQL — خدمة واحدة بالضبط، وهي BFF. تحتاج واجهة patient-360 في الواجهة الأمامية بيانات من خمس خدمات بشكل يحدده العميل. وفعل ذلك عبر REST يعني إما خمس رحلات ذهاب وإياب أو نقطة تجميع مخصصة لكل شاشة — Spring for GraphQL، بنهج schema-first، مع تفويض على مستوى الحقول وحدود لعمق الاستعلامات وتعقيدها.
gRPC — بين الخدمات الداخلية فقط، ومتمركز في terminology-service. حمولات أصغر من كيلوبايت يشكل فيها تغليف JSON جزءًا كبيرًا من الرسالة، تُستدعى في كل كتابة سريرية تقريبًا، بمخطط لا يتغير شكله أبدًا في الواقع. والجمع بين DataLoader و gRPC في BFF هو التصميم الذي أشير إليه أولًا: تبدو المحللات وكأنها تجلب البيانات حقلًا بحقل، لكن DataLoader يجمع كل المعرّفات خلال دورة واحدة من حلقة الأحداث ويوزعها باستدعاء gRPC واحد لكل خدمة — انظر مخطط Patient 360 أعلاه لترى كيف يحدث ذلك بالضبط.