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

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

מה תפַתחו

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

מה תלמדו

  • איך יוצרים שרת HAPI FHIR מקומי באמצעות Docker
  • איך משלבים את ספריית FHIR Engine באפליקציה ל-Android
  • איך משתמשים ב-Sync API כדי להגדיר משימה חד-פעמית או תקופתית להורדה ולהעלאה של משאבי FHIR
  • איך משתמשים ב-Search API
  • איך משתמשים בממשקי API לגישה לנתונים כדי ליצור, לקרוא, לעדכן ולמחוק משאבי 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
    
    עם זאת, השלמת התהליך הזה עשויה להימשך זמן רב, והוא לא נחוץ ל-codelab.
  4. כדי לוודא שנתוני הבדיקה זמינים בשרת, פותחים את כתובת ה-URL http://localhost:8080/fhir/Patient/ בדפדפן. תוצאת החיפוש אמורה להיות הטקסט HTTP 200 OK והקטע Response Body בדף שמכיל נתוני מטופלים ב-FHIR Bundle, עם ספירה של total.בדיקת נתונים בשרת

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

הורדת הקוד

כדי להוריד את הקוד של ה-codelab הזה, משכפלים את מאגר 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.) (ייבוא פרויקט (Gradle,‏ Eclipse ADT וכו')) ובוחרים את התיקייה codelabs/engine/ מקוד המקור שהורדתם קודם.

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

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

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

dependencies {
    // ...

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

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

בסרגל הכלים של Android Studio, לוחצים על Sync Project with Gradle Files (סנכרון הפרויקט עם קובצי Gradle) ‏(לחצן הסנכרון של Gradle). אפשר גם להריץ את האפליקציה שוב כדי לבדוק שהתלות פועלת בצורה תקינה.

הפעלת האפליקציה לתחילת הדרך

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

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

אפליקציית Hello World

4. יצירת מופע של FHIR Engine

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

  1. עוברים למחלקה Application, שבמקרה הזה היא 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 in 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 Engine:מתחילים בקבלת הפניה ל-FHIR Engine ב-PatientListViewModel.kt.
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    הקוד הזה מפעיל שגרת המשך (coroutine) בהיקף של ViewModel ומאתחל את מנוע FHIR.
  2. חיפוש מטופלים מ-Wakefield:משתמשים במנוע FHIR כדי לחפש מטופלים שכתובת העיר שלהם היא Wakefield.
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    בדוגמה הזו, אנחנו משתמשים בשיטה search של מנוע FHIR כדי לסנן מטופלים על סמך העיר שמופיעה בכתובת שלהם. התוצאה תהיה רשימה של מטופלים מ-Wakefield.
  3. חיפוש מטופלים מהעיר טאונטון:באופן דומה, מחפשים מטופלים שהעיר בכתובת שלהם היא Taunton.
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    עכשיו יש לנו שתי רשימות של מטופלים – אחת מ-Wakefield והשנייה מ-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 באפליקציית Android משלכם

מידע נוסף