إدارة موارد FHIR باستخدام مكتبة محرك FHIR

‫1. قبل البدء

ما ستنشئه

في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيق Android باستخدام مكتبة FHIR Engine. سيستخدم تطبيقك مكتبة FHIR Engine لتنزيل موارد FHIR من خادم FHIR، وتحميل أي تغييرات محلية إلى الخادم.

أهداف الدورة التعليمية

  • كيفية إنشاء خادم HAPI FHIR محلي باستخدام Docker
  • كيفية دمج مكتبة FHIR Engine في تطبيق Android
  • كيفية استخدام Sync API لإعداد مهمة لمرة واحدة أو بشكل دوري لتنزيل موارد FHIR وتحميلها
  • كيفية استخدام Search API
  • كيفية استخدام واجهات برمجة التطبيقات Data Access لإنشاء موارد FHIR وقراءتها وتعديلها وحذفها محليًا

المتطلبات

إذا لم يسبق لك إنشاء تطبيقات Android، يمكنك البدء بإنشاء تطبيقك الأول.

‫2. إعداد خادم HAPI FHIR محلي باستخدام بيانات اختبار

HAPI FHIR هو خادم FHIR مفتوح المصدر وشائع الاستخدام. نستخدم خادم HAPI FHIR محليًا في درس تطبيقي حول الترميز ليتصل به تطبيق Android.

إعداد خادم HAPI FHIR المحلّي

  1. نفِّذ الأمر التالي في الوحدة الطرفية للحصول على أحدث صورة من HAPI FHIR
    docker pull hapiproject/hapi:latest
    
  2. أنشئ حاوية HAPI FHIR إما باستخدام Docker Desktop لتشغيل الصورة التي تم تنزيلها سابقًا hapiproject/hapi، أو بتنفيذ الأمر التالي
    docker run -p 8080:8080 hapiproject/hapi:latest
    
    مزيد من المعلومات
  3. افحص الخادم من خلال فتح عنوان URL http://localhost:8080/ في المتصفّح. من المفترض أن تظهر لك واجهة الويب HAPI FHIR.واجهة الويب HAPI FHIR

ملء خادم HAPI FHIR المحلي ببيانات الاختبار

لاختبار تطبيقنا، سنحتاج إلى بعض بيانات الاختبار على الخادم. سنستخدم بيانات اصطناعية من إنشاء Synthea.

  1. أولاً، علينا تنزيل بيانات نموذجية من synthea-samples. نزِّل الملف synthea_sample_data_fhir_r4_sep2019.zip واستخرِجه. تحتوي بيانات العيّنة غير المضغوطة على ملفات .json عديدة، كلّ منها عبارة عن حزمة معاملات لمريض فردي.
  2. سنحمّل بيانات اختبارية لثلاثة مرضى إلى خادم HAPI FHIR المحلي. نفِّذ الأمر التالي في الدليل الذي يحتوي على ملفات JSON
    curl -X POST -H "Content-Type: application/json" -d @./Aaron697_Brekke496_2fa15bc7-8866-461a-9000-f739e425860a.json http://localhost:8080/fhir/
    curl -X POST -H "Content-Type: application/json" -d @./Aaron697_Stiedemann542_41166989-975d-4d17-b9de-17f94cb3eec1.json http://localhost:8080/fhir/
    curl -X POST -H "Content-Type: application/json" -d @./Abby752_Kuvalis369_2b083021-e93f-4991-bf49-fd4f20060ef8.json http://localhost:8080/fhir/
    
  3. لتحميل بيانات الاختبار لجميع المرضى إلى الخادم، شغِّل
    for f in *.json; do curl -X POST -H "Content-Type: application/json" -d @$f http://localhost:8080/fhir/ ; done
    
    ومع ذلك، قد يستغرق إكمال هذه العملية وقتًا طويلاً وهي غير ضرورية في هذا الدرس العملي.
  4. تأكَّد من توفّر بيانات الاختبار على الخادم من خلال فتح عنوان URL http://localhost:8080/fhir/Patient/ في المتصفّح. من المفترض أن يظهر لك النص HTTP 200 OK وقسم Response Body من الصفحة التي تحتوي على بيانات المرضى في حزمة FHIR كنتيجة البحث مع عدد total.بيانات الاختبار على الخادم

3- إعداد تطبيق Android

تنزيل الرمز

لتنزيل الرمز البرمجي الخاص بهذا الدرس التطبيقي حول الترميز، أنشئ نسخة طبق الأصل من مستودع حزمة تطوير البرامج (SDK) لنظام التشغيل Android FHIR: git clone https://github.com/google/android-fhir.git

يقع مشروع البداية الخاص بهذا الدرس العملي حول الترميز في codelabs/engine.

استيراد التطبيق إلى استوديو Android

نبدأ باستيراد تطبيق أوّلي إلى استوديو Android.

افتح "استوديو Android"، وانقر على استيراد مشروع (Gradle وEclipse ADT وما إلى ذلك)، ثم اختَر مجلد codelabs/engine/ من الرمز المصدري الذي نزّلته سابقًا.

شاشة بدء "استوديو Android"

مزامنة مشروعك مع ملفات Gradle

لتسهيل الأمر عليك، تمت إضافة تبعيات مكتبة FHIR Engine إلى المشروع. يتيح لك ذلك دمج مكتبة FHIR Engine في تطبيقك. راجِع الأسطر التالية في نهاية ملف app/build.gradle.kts الخاص بمشروعك:

dependencies {
    // ...

    implementation("com.google.android.fhir:engine:1.1.0")
}

للتأكّد من توفّر جميع التبعيات لتطبيقك، عليك مزامنة مشروعك مع ملفات Gradle في هذه المرحلة.

انقر على مزامنة المشروع مع ملفات Gradle (زر مزامنة Gradle) من شريط أدوات "استوديو Android". يمكنك أيضًا تشغيل التطبيق مرة أخرى للتأكّد من أنّ التبعيات تعمل بشكل صحيح.

تشغيل التطبيق النموذجي

بعد استيراد المشروع إلى "استوديو Android"، يمكنك تشغيل التطبيق للمرة الأولى.

ابدأ تشغيل محاكي "استوديو Android"، ثم انقر على "تشغيل" (زر التشغيل) في شريط أدوات "استوديو Android".

تطبيق Hello World

4. إنشاء مثيل FHIR Engine

لدمج FHIR Engine في تطبيق Android، عليك استخدام مكتبة FHIR Engine وإنشاء مثيل من FHIR Engine. ستساعدك الخطوات الموضّحة أدناه في إكمال العملية.

  1. انتقِل إلى فئة التطبيق، وهي FhirApplication.kt في هذا المثال، والموجودة في app/src/main/java/com/google/android/fhir/codelabs/engine.
  2. داخل طريقة onCreate()، أضِف الرمز التالي لتهيئة FHIR Engine:
      FhirEngineProvider.init(
          FhirEngineConfiguration(
            enableEncryptionIfSupported = true,
            RECREATE_AT_OPEN,
            ServerConfiguration(
              baseUrl = "http://10.0.2.2:8080/fhir/",
              httpLogger =
                HttpLogger(
                  HttpLogger.Configuration(
                    if (BuildConfig.DEBUG) HttpLogger.Level.BODY else HttpLogger.Level.BASIC,
                  ),
                ) {
                  Log.d("App-HttpLog", it)
                },
            ),
          ),
      )
    
    ملاحظات:
    • enableEncryptionIfSupported: يفعّل تشفير البيانات إذا كان الجهاز يتيح ذلك.
    • RECREATE_AT_OPEN: تحدّد استراتيجية أخطاء قاعدة البيانات. في هذه الحالة، يعيد إنشاء قاعدة البيانات إذا حدث خطأ عند فتحها.
    • baseUrl في ServerConfiguration: هذا هو عنوان URL الأساسي لخادم FHIR. عنوان IP 10.0.2.2 المقدَّم محجوز خصيصًا للمضيف المحلي، ويمكن الوصول إليه من محاكي Android. مزيد من المعلومات
  3. في فئة FhirApplication، أضِف السطر التالي لإنشاء مثيل FHIR Engine بشكل غير مباشر:
      private val fhirEngine: FhirEngine by
          lazy { FhirEngineProvider.getInstance(this) }
    
    يضمن ذلك عدم إنشاء مثيل FhirEngine إلا عند الوصول إليه للمرة الأولى، وليس فور بدء تشغيل التطبيق.
  4. أضِف طريقة المساعدة التالية في الفئة FhirApplication لتسهيل الوصول إليها في جميع أنحاء تطبيقك:
    companion object {
        fun fhirEngine(context: Context) =
            (context.applicationContext as FhirApplication).fhirEngine
    }
    
    تتيح لك هذه الطريقة الثابتة استرداد مثيل FHIR Engine من أي مكان في التطبيق باستخدام السياق.

5. مزامنة البيانات مع خادم FHIR

  1. أنشئ صفًا جديدًا DownloadWorkManagerImpl.kt. في هذه الفئة، ستحدّد الطريقة التي يسترد بها التطبيق المورد التالي من القائمة لتنزيله:
      class DownloadWorkManagerImpl : DownloadWorkManager {
        private val urls = LinkedList(listOf("Patient"))
    
        override suspend fun getNextRequest(): DownloadRequest? {
          val url = urls.poll() ?: return null
          return DownloadRequest.of(url)
        }
    
        override suspend fun getSummaryRequestUrls() = mapOf<ResourceType, String>()
    
        override suspend fun processResponse(response: Resource): Collection<Resource> {
          var bundleCollection: Collection<Resource> = mutableListOf()
          if (response is Bundle && response.type == Bundle.BundleType.SEARCHSET) {
            bundleCollection = response.entry.map { it.resource }
          }
          return bundleCollection
        }
      }
    
    يحتوي هذا الصف على قائمة انتظار لأنواع الموارد التي يريد تنزيلها. تعالج هذه العملية الردود وتستخرج الموارد من الحزمة التي تم إرجاعها، ويتم حفظها في قاعدة البيانات المحلية.
  2. إنشاء فئة جديدة AppFhirSyncWorker.kt تحدّد هذه الفئة كيفية مزامنة التطبيق مع خادم FHIR البعيد باستخدام عملية تعمل في الخلفية.
    class AppFhirSyncWorker(appContext: Context, workerParams: WorkerParameters) :
      FhirSyncWorker(appContext, workerParams) {
    
      override fun getDownloadWorkManager() = DownloadWorkManagerImpl()
    
      override fun getConflictResolver() = AcceptLocalConflictResolver
    
      override fun getFhirEngine() = FhirApplication.fhirEngine(applicationContext)
    
      override fun getUploadStrategy() =
        UploadStrategy.forBundleRequest(
          methodForCreate = HttpCreateMethod.PUT,
          methodForUpdate = HttpUpdateMethod.PATCH,
          squash = true,
          bundleSize = 500,
        )
    }
    
    في هذا القسم، حدّدنا مدير التنزيل وحل التعارض ومثيل محرّك FHIR الذي سيتم استخدامه للمزامنة.
  3. في ViewModel، PatientListViewModel.kt، عليك إعداد آلية مزامنة لمرة واحدة. ابحث عن الرمز التالي وأضِفه إلى الدالة triggerOneTimeSync():
    viewModelScope.launch {
          Sync.oneTimeSync<AppFhirSyncWorker>(getApplication())
            .shareIn(this, SharingStarted.Eagerly, 10)
            .collect { _pollState.emit(it) }
        }
    
    تبدأ روتين الإجراءات الفرعي هذا عملية مزامنة لمرة واحدة مع خادم FHIR باستخدام AppFhirSyncWorker الذي حدّدناه سابقًا. سيتم بعد ذلك تعديل واجهة المستخدِم استنادًا إلى حالة عملية المزامنة.
  4. في ملف PatientListFragment.kt، عدِّل نص الدالة handleSyncJobStatus:
    when (syncJobStatus) {
        is SyncJobStatus.Finished -> {
            Toast.makeText(requireContext(), "Sync Finished", Toast.LENGTH_SHORT).show()
            viewModel.searchPatientsByName("")
        }
        else -> {}
    }
    
    عند انتهاء عملية المزامنة، ستظهر رسالة تنبيه للمستخدم، ثم سيعرض التطبيق جميع المرضى من خلال طلب البحث باسم فارغ.

بعد الانتهاء من إعداد كل شيء، شغِّل تطبيقك. انقر على الزر Sync في القائمة. إذا كان كل شيء يعمل بشكل صحيح، من المفترض أن ترى المرضى من خادم FHIR المحلي يتم تنزيلهم وعرضهم في التطبيق.

قائمة المرضى

6. تعديل بيانات المرضى وتحميلها

في هذا القسم، سنقدّم لك إرشادات حول عملية تعديل بيانات المرضى استنادًا إلى معايير محدّدة وتحميل البيانات المعدَّلة إلى خادم FHIR. على وجه التحديد، سنبدّل مدن العناوين للمرضى المقيمين في Wakefield وTaunton.

الخطوة 1: إعداد منطق التعديل في PatientListViewModel

تتم إضافة الرمز البرمجي في هذا القسم إلى الدالة triggerUpdate في PatientListViewModel

  1. الوصول إلى FHIR Engine:ابدأ بالحصول على مرجع إلى FHIR Engine في PatientListViewModel.kt.
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    يُطلق هذا الرمز روتينًا فرعيًا ضمن نطاق ViewModel ويُهيّئ محرّك FHIR.
  2. البحث عن مرضى من ويكفيلد:استخدِم محرّك FHIR للبحث عن مرضى لديهم مدينة عنوان Wakefield.
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    في هذا المثال، نستخدم طريقة search في محرّك FHIR لفلترة المرضى استنادًا إلى مدينة عنوانهم. ستكون النتيجة قائمة بالمرضى من ويكفيلد.
  3. البحث عن مرضى من مدينة "تونتون":يمكنك أيضًا البحث عن مرضى عنوان مدينتهم هو Taunton.
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    لدينا الآن قائمتان بالمرضى، إحداهما من ويكفيلد والأخرى من تونتون.
  4. تعديل عناوين المرضى:راجِع كل مريض في قائمة patientsFromWakefield، وغيِّر مدينته إلى Taunton، وعدِّل بياناته في محرّك FHIR.
    patientsFromWakefield.forEach {
         it.resource.address.first().city = "Taunton"
         fhirEngine.update(it.resource)
    }
    
    وبالمثل، عدِّل كل مريض في قائمة patientsFromTaunton لتغيير مدينته إلى Wakefield.
    patientsFromTaunton.forEach {
         it.resource.address.first().city = "Wakefield"
         fhirEngine.update(it.resource)
    }
    
  5. بدء المزامنة:بعد تعديل البيانات على الجهاز، ابدأ عملية مزامنة لمرة واحدة لضمان تعديل البيانات على خادم FHIR.
    triggerOneTimeSync()
    }
    
    تشير علامة الإغلاق } إلى نهاية الروتين الفرعي الذي تم تشغيله في البداية.

الخطوة 2: اختبار الوظيفة

  1. اختبار واجهة المستخدم:شغِّل تطبيقك. انقر على الزر Update في القائمة. من المفترض أن يظهر لك أنّه تم تبديل مدن العناوين للمريضَين Aaron697 وAbby752.
  2. التحقّق من الخادم:افتح متصفّحًا وانتقِل إلى http://localhost:8080/fhir/Patient/. تأكَّد من تعديل مدينة العنوان للمريضَين Aaron697 وAbby752 على خادم FHIR المحلي.

باتّباع هذه الخطوات، تكون قد نفّذت بنجاح آلية لتعديل بيانات المرضى ومزامنة التغييرات مع خادم FHIR.

7. البحث عن المرضى حسب الاسم

يمكن أن يوفّر البحث عن المرضى بأسمائهم طريقة سهلة الاستخدام لاسترداد المعلومات. سنشرح لك هنا عملية تنفيذ هذه الميزة في تطبيقك.

الخطوة 1: تعديل توقيع الدالة

انتقِل إلى ملف PatientListViewModel.kt وابحث عن الدالة المسماة searchPatientsByName. سنضيف رمزًا إلى هذه الدالة.

لفلترة النتائج استنادًا إلى طلب البحث عن الاسم المقدَّم وعرض النتائج ليتم تعديل واجهة المستخدم، يمكنك دمج مجموعة الرموز الشرطية التالية:

    viewModelScope.launch {
      val fhirEngine = FhirApplication.fhirEngine(getApplication())
      if (nameQuery.isNotEmpty()) {
        val searchResult = fhirEngine.search<Patient> {
          filter(
            Patient.NAME,
            {
              modifier = StringFilterModifier.CONTAINS
              value = nameQuery
            },
          )
        }
        liveSearchedPatients.value  =  searchResult.map { it.resource }
      }
    }

في هذه الحالة، إذا لم يكن الحقل nameQuery فارغًا، ستعمل دالة البحث على فلترة النتائج لتضمين المرضى الذين تتضمّن أسماؤهم طلب البحث المحدّد فقط.

الخطوة 2: اختبار وظيفة البحث الجديدة

  1. إعادة تشغيل التطبيق:بعد إجراء هذه التغييرات، أعِد إنشاء تطبيقك وشغِّله.
  2. البحث عن المرضى: في شاشة قائمة المرضى، استخدِم وظيفة البحث. من المفترض أن تتمكّن الآن من إدخال اسم (أو جزء من اسم) لفلترة قائمة المرضى وفقًا لذلك.

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

8. تهانينا!

لقد استخدمت مكتبة FHIR Engine لإدارة موارد FHIR في تطبيقك:

  • استخدام Sync API لمزامنة موارد FHIR مع خادم FHIR
  • استخدام Data Access API لإنشاء موارد FHIR محلية وقراءتها وتعديلها وحذفها
  • استخدام Search API للبحث عن موارد FHIR محلية

المواضيع التي تناولناها

  • كيفية إعداد خادم HAPI FHIR محلي
  • كيفية تحميل بيانات الاختبار إلى خادم HAPI FHIR المحلّي
  • كيفية إنشاء تطبيق Android باستخدام مكتبة FHIR Engine
  • كيفية استخدام واجهة برمجة التطبيقات Sync API وData Access API وSearch API في مكتبة FHIR Engine

الخطوات التالية

  • استكشاف مستندات FHIR Engine Library
  • استكشاف الميزات المتقدّمة لواجهة برمجة التطبيقات Search API
  • تطبيق FHIR Engine Library في تطبيق Android الخاص بك

مزيد من المعلومات