ניהול משאבי FHIR באמצעות FHIR Engine Library

1. לפני שמתחילים

מה תפַתחו

בסדנת הקוד הזו תלמדו ליצור אפליקציית Android באמצעות ספריית FHIR Engine. האפליקציה תשתמש בספריית FHIR Engine כדי להוריד משאבים של FHIR משרת FHIR, ולהעלות שינויים מקומיים לשרת.

מה תלמדו

  • איך יוצרים שרת HAPI FHIR מקומי באמצעות Docker
  • איך משלבים את ספריית FHIR Engine באפליקציה ל-Android
  • איך משתמשים ב-Sync API כדי להגדיר משימה חד-פעמית או תקופתית להורדה ולהעלאה של משאבי FHIR
  • איך משתמשים ב-Search API
  • איך משתמשים ב-Data Access APIs כדי ליצור, לקרוא, לעדכן ולמחוק משאבי FHIR באופן מקומי

מה נדרש

אם עדיין לא יצרתם אפליקציות ל-Android, תוכלו להתחיל בפיתוח האפליקציה הראשונה.

2. הגדרת שרת HAPI FHIR מקומי עם נתוני בדיקה

HAPI FHIR הוא שרת FHIR פופולרי בקוד פתוח. אנחנו משתמשים בשרת HAPI FHIR מקומי ב-codelab שלנו, שאליו מתחברת אפליקציית 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 Bundle אמורים להופיע כתוצאת החיפוש עם ספירה של total.בדיקת נתונים בשרת

3. הגדרת האפליקציה ל-Android

הורדת הקוד

כדי להוריד את הקוד של סדנת הקוד הזו, צריך לשכפל את המאגר של Android FHIR SDK: git clone https://github.com/google/android-fhir.git

הפרויקט למתחילים ב-Codelab הזה נמצא ב-codelabs/engine.

ייבוא האפליקציה ל-Android Studio

מתחילים בייבוא האפליקציה למתחילים ל-Android Studio.

פותחים את Android Studio, בוחרים באפשרות Import Project (Gradle, Eclipse ADT, etc.) ובוחרים את התיקייה codelabs/engine/ מקוד המקור שהורדתם מקודם.

מסך ההתחלה של Android Studio

סנכרון הפרויקט עם קובצי Gradle

לנוחותכם, יחסי התלות של ספריית FHIR Engine כבר נוספו לפרויקט. כך תוכלו לשלב את ספריית FHIR Engine באפליקציה. מוסיפים את השורות הבאות עד לסוף הקובץ app/build.gradle.kts של הפרויקט:

dependencies {
    // ...

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

כדי לוודא שכל יחסי התלות זמינים לאפליקציה, צריך לסנכרן את הפרויקט עם קובצי Gradle בשלב הזה.

בוחרים באפשרות סנכרון הפרויקט עם קובצי Gradle (לחצן הסנכרון של Gradle) בסרגל הכלים של Android Studio. אפשר גם להריץ את האפליקציה שוב כדי לבדוק אם יחסי התלות פועלים כמו שצריך.

הפעלת האפליקציה למתחילים

עכשיו, אחרי שמייבאים את הפרויקט ל-Android Studio, אפשר להריץ את האפליקציה בפעם הראשונה.

מפעילים את המהדר של Android Studio ולוחצים על סמל ההפעלה (לחצן ההפעלה) בסרגל הכלים של Android Studio.

אפליקציית 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, שמורה במיוחד ל-localhost, וניתן לגשת אליה מהמכונה הווירטואלית של 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) }
        }
    
    פונקציית ה-coroutine הזו מפעילה סנכרון חד-פעמי עם שרת 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:מתחילים בקבלת הפניה למנוע FHIR ב-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()
    }
    
    סוגריים מסולסלים סוגריים } מסמנים את הסוף של פונקציית ה-coroutine שהופעל בהתחלה.

שלב 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
  • תכונות מתקדמות של Search API
  • שימוש בספריית FHIR Engine באפליקציה ל-Android

מידע נוסף