FHIR 엔진 라이브러리를 사용하여 FHIR 리소스 관리

1. 시작하기 전에

빌드할 항목

이 Codelab에서는 FHIR 엔진 라이브러리를 사용하여 Android 앱을 빌드합니다. 앱은 FHIR 엔진 라이브러리를 사용하여 FHIR 서버에서 FHIR 리소스를 다운로드하고 로컬 변경사항을 서버에 업로드합니다.

학습할 내용

  • Docker를 사용하여 로컬 HAPI FHIR 서버를 만드는 방법
  • Android 애플리케이션에 FHIR Engine 라이브러리를 통합하는 방법
  • 동기화 API를 사용하여 FHIR 리소스를 다운로드하고 업로드하는 일회성 또는 주기적 작업을 설정하는 방법
  • Search API 사용 방법
  • 데이터 액세스 API를 사용하여 FHIR 리소스를 로컬로 생성, 읽기, 업데이트, 삭제하는 방법

필요한 항목

Android 앱을 빌드한 적이 없다면 첫 앱 빌드로 시작하세요.

2. 테스트 데이터로 로컬 HAPI FHIR 서버 설정

HAPI FHIR은 널리 사용되는 오픈소스 FHIR 서버입니다. Android 앱이 연결할 수 있도록 Codelab에서 로컬 HAPI FHIR 서버를 사용합니다.

로컬 HAPI FHIR 서버 설정

  1. 터미널에서 다음 명령어를 실행하여 HAPI FHIR의 최신 이미지를 가져옵니다.
    docker pull hapiproject/hapi:latest
    
  2. Docker Desktop을 사용하여 이전에 다운로드한 이미지 hapiproject/hapi를 실행하거나 다음 명령어를 실행하여 HAPI FHIR 컨테이너를 만듭니다.
    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 텍스트와 FHIR 번들에 환자 데이터가 포함된 페이지의 Response Body 섹션이 total 개수의 검색 결과로 표시됩니다.서버의 테스트 데이터

3. Android 앱 설정

코드 다운로드

이 Codelab의 코드를 다운로드하려면 Android FHIR SDK 저장소를 클론하세요. git clone https://github.com/google/android-fhir.git

이 Codelab의 시작 프로젝트는 codelabs/engine에 있습니다.

Android 스튜디오로 앱 가져오기

먼저 시작 앱을 Android 스튜디오로 가져옵니다.

Android 스튜디오를 열고 Import Project (Gradle, Eclipse ADT, etc.)를 선택한 다음 이전에 다운로드한 소스 코드에서 codelabs/engine/ 폴더를 선택합니다.

Android 스튜디오 시작 화면

프로젝트를 Gradle 파일과 동기화

편의를 위해 FHIR 엔진 라이브러리 종속 항목이 이미 프로젝트에 추가되어 있습니다. 이렇게 하면 앱에 FHIR Engine 라이브러리를 통합할 수 있습니다. 프로젝트의 app/build.gradle.kts 파일 끝에 다음 줄이 표시됩니다.

dependencies {
    // ...

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

앱에서 모든 종속 항목을 사용할 수 있도록 하려면 이 시점에서 프로젝트를 Gradle 파일과 동기화해야 합니다.

Android 스튜디오 툴바에서 Sync Project with Gradle Files (Gradle 동기화 버튼)를 선택합니다. 앱을 다시 실행하여 종속 항목이 올바르게 작동하는지 확인할 수도 있습니다.

시작 앱 실행

이제 Android 스튜디오로 프로젝트를 가져왔으므로 앱을 처음으로 실행할 수 있습니다.

Android 스튜디오 에뮬레이터를 시작하고 Android 스튜디오 툴바에서 실행 (실행 버튼)을 클릭합니다.

Hello World 앱

4. FHIR Engine 인스턴스 만들기

Android 앱에 FHIR 엔진을 통합하려면 FHIR 엔진 라이브러리를 사용하고 FHIR 엔진의 인스턴스를 시작해야 합니다. 아래 설명된 단계에서는 이 프로세스를 안내합니다.

  1. Application 클래스(이 예에서는 app/src/main/java/com/google/android/fhir/codelabs/engine에 있는 FhirApplication.kt)로 이동합니다.
  2. onCreate() 메서드 내에서 다음 코드를 추가하여 FHIR 엔진을 초기화합니다.
      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: 데이터베이스 오류 전략을 결정합니다. 이 경우 열 때 오류가 발생하면 데이터베이스를 다시 만듭니다.
    • ServerConfigurationbaseUrl: FHIR 서버의 기본 URL입니다. 제공된 IP 주소 10.0.2.2는 localhost용으로 특별히 예약되어 있으며 Android 에뮬레이터에서 액세스할 수 있습니다. 자세히 알아보세요.
  3. FhirApplication 클래스에서 다음 줄을 추가하여 FHIR 엔진을 지연 인스턴스화합니다.
      private val fhirEngine: FhirEngine by
          lazy { FhirEngineProvider.getInstance(this) }
    
    이렇게 하면 FhirEngine 인스턴스가 앱이 시작될 때 즉시 생성되지 않고 처음 액세스할 때만 생성됩니다.
  4. 애플리케이션 전체에서 더 쉽게 액세스할 수 있도록 FhirApplication 클래스에 다음 편의 메서드를 추가합니다.
    companion object {
        fun fhirEngine(context: Context) =
            (context.applicationContext as FhirApplication).fhirEngine
    }
    
    이 정적 메서드를 사용하면 컨텍스트를 사용하여 앱의 어느 곳에서나 FHIR 엔진 인스턴스를 검색할 수 있습니다.

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) }
        }
    
    이 코루틴은 앞에서 정의한 AppFhirSyncWorker를 사용하여 FHIR 서버와의 일회성 동기화를 시작합니다. 그런 다음 동기화 프로세스의 상태에 따라 UI가 업데이트됩니다.
  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 서버에 업로드하는 과정을 안내합니다. 구체적으로 WakefieldTaunton에 거주하는 환자의 주소 도시가 서로 바뀝니다.

1단계: PatientListViewModel에서 수정 로직 설정

이 섹션의 코드는 PatientListViewModeltriggerUpdate 함수에 추가됩니다.

  1. FHIR 엔진 액세스:PatientListViewModel.kt에서 FHIR 엔진에 대한 참조를 가져와 시작합니다.
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    이 코드는 ViewModel 범위 내에서 코루틴을 실행하고 FHIR 엔진을 초기화합니다.
  2. Wakefield의 환자 검색:FHIR 엔진을 사용하여 주소 도시가 Wakefield인 환자를 검색합니다.
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    여기서는 FHIR 엔진의 search 메서드를 사용하여 주소 도시를 기준으로 환자를 필터링합니다. 결과는 Wakefield의 환자 목록입니다.
  3. Taunton의 환자 검색:마찬가지로 주소 도시가 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. UI 테스트:앱을 실행합니다. 메뉴에서 Update 버튼을 클릭합니다. 환자 Aaron697Abby752의 주소 도시가 바뀌어 표시됩니다.
  2. 서버 확인:브라우저를 열고 http://localhost:8080/fhir/Patient/로 이동합니다. 환자 Aaron697Abby752의 주소 도시가 로컬 FHIR 서버에서 업데이트되었는지 확인합니다.

이 단계를 따르면 환자 데이터를 수정하고 변경사항을 FHIR 서버와 동기화하는 메커니즘을 성공적으로 구현한 것입니다.

7. 이름으로 환자 검색

이름으로 환자를 검색하면 정보를 사용자 친화적인 방식으로 검색할 수 있습니다. 여기에서는 애플리케이션에서 이 기능을 구현하는 과정을 안내합니다.

1단계: 함수 서명 업데이트

PatientListViewModel.kt 파일로 이동하여 searchPatientsByName이라는 함수를 찾습니다. 이 함수에 코드를 추가할 것입니다.

제공된 이름 쿼리를 기반으로 결과를 필터링하고 UI가 업데이트되도록 결과를 내보내려면 다음 조건부 코드 블록을 통합하세요.

    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 엔진 라이브러리를 사용하여 앱에서 FHIR 리소스를 관리했습니다.

  • 동기화 API를 사용하여 FHIR 리소스를 FHIR 서버와 동기화
  • 데이터 액세스 API를 사용하여 로컬 FHIR 리소스 생성, 읽기, 업데이트, 삭제
  • 검색 API를 사용하여 로컬 FHIR 리소스 검색

학습한 내용

  • 로컬 HAPI FHIR 서버를 설정하는 방법
  • 테스트 데이터를 로컬 HAPI FHIR 서버에 업로드하는 방법
  • FHIR 엔진 라이브러리를 사용하여 Android 앱을 빌드하는 방법
  • FHIR Engine 라이브러리에서 동기화 API, 데이터 액세스 API, 검색 API를 사용하는 방법

다음 단계

  • FHIR Engine 라이브러리 문서 살펴보기
  • Search API의 고급 기능 살펴보기
  • 자체 Android 앱에 FHIR 엔진 라이브러리 적용

자세히 알아보기