FHIR 번들을 사용하여 FHIR 리소스 관리

이 페이지에서는 FHIR 번들을 실행하여 FHIR 리소스를 관리하는 방법을 설명합니다. ExecuteBundle 메서드는 FHIR 표준 배치/트랜잭션 상호작용(DSTU2, STU3, R4)을 구현합니다.

FHIR 번들

FHIR 번들에는 관찰 또는 환자 같은 리소스에 대한 만들기, 업데이트, 삭제 등의 작업을 나타내는 항목 배열이 포함됩니다. 번들 리소스에서 요소에 대한 자세한 설명을 참조하세요.

FHIR 번들이 실행될 때는 번들 유형에 따라 번들의 작업 수행 방법이 결정됩니다. 번들 유형을 batch로 설정하면 작업이 여러 개의 개별 요청으로 실행됩니다. 번들 유형을 transaction으로 설정하면 작업이 서로 종속된 여러 요청으로 작업이 실행됩니다. 예를 들어 트랜잭션 번들에 환자 및 관찰 만들기가 포함된 경우 환자 만들기 요청이 실패하면 관찰이 생성되지 않습니다.

번들 유형이 batch인 경우 작업이 실패하면 Cloud Healthcare API가 번들에서 나머지 작업을 실행합니다. 번들 유형이 transaction경 겨우 작업이 실패하면 Cloud Healthcare API가 작업 실행을 중지하고 트랜잭션을 롤백합니다.

번들 실행에 대한 권한 부여

번들을 실행하려면 datasets.fhirStores.fhir.executeBundle 권한 역할이 필요합니다. 이 권한을 부여하려면 healthcare.fhirResourceReader 역할을 사용하세요. 이 권한을 부여하는 단계는 정책 수정을 참조하세요.

Cloud Healthcare API는 번들의 각 작업에 대한 권한을 확인합니다. healthcare.fhirResources.create 권한만 있고 healthcare.fhirResources.update 권한이 없는 경우 healthcare.fhirResources.create 작업을 포함하는 번들만 실행할 수 있습니다.

번들 실행

FHIR 번들을 실행하려면 projects.locations.datasets.fhirStores.fhir.executeBundle 메서드를 사용합니다.

다음 샘플에서 BUNDLE.json은 JSON으로 인코딩된 FHIR 번들의 경로 및 파일 이름입니다. 요청 본문에 번들을 포함할 수도 있습니다.

다음 샘플 번들은 환자 리소스를 만들고 다른 환자 리소스를 삭제합니다.

{
  "resourceType": "Bundle",
  "id": "bundle-transaction",
  "meta": {
    "lastUpdated": "2018-03-11T11:22:16Z"
  },
  "type": "transaction",
  "entry": [
    {
      "resource": {
        "resourceType": "Patient",
        "name": [
          {
            "family": "Smith",
            "given": [
              "Darcy"
            ]
          }
        ],
        "gender": "female",
        "address": [
          {
            "line": [
              "123 Main St."
            ],
            "city": "Anycity",
            "state": "CA",
            "postalCode": "12345"
          }
        ]
      },
      "request": {
        "method": "POST",
    "url": "Patient"
      }
    },
    {
      "request": {
        "method": "DELETE",
        "url": "Patient/1234567890"
      }
    }
  ]
}

다음 샘플은 번들 실행 방법을 보여줍니다.

curl

번들을 실행하려면 POST 요청을 수행하고 다음 정보를 지정합니다.

  • 상위 데이터 세트와 FHIR 저장소의 이름 및 위치
  • Cloud Storage에서 번들 파일의 위치
  • 액세스 토큰

다음 샘플은 curl을 사용하는 POST 요청을 보여줍니다.

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @BUNDLE_FILE.json \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir"

개별 작업의 결과와 관계없이 배치 번들을 실행한 후 서버는 batch-response 유형 Bundle 리소스의 JSON 인코딩 표현을 반환합니다. Bundle 리소스는 요청에 입력한 내용과 해당 입력을 처리한 결과를 하나의 항목으로 포함합니다. 이 결과에는 성공 결과와 오류 결과가 혼합되어 있을 수 있습니다

트랜잭션 번들이 성공하면 서버는 요청에 입력한 내용과 성공적인 작업 결과를 하나의 항목으로 포함한 transaction-response 유형 Bundle 리소스의 JSON 인코딩 표현을 반환합니다.

트랜잭션 번들을 실행하는 동안 오류가 발생하면 응답 본문에 번들이 포함되지 않습니다. 대신 오류가 발생한 이유를 설명하는 JSON 인코딩 OperationOutcome 리소스가 포함됩니다. 롤백한 성공적인 작업은 응답에 보고되지 않습니다.

다음 샘플 번들은 위 예시를 성공적으로 실행한 출력입니다. 첫 번째 항목은 환자 만들기 작업의 성공을 나타내고 새 리소스의 ID를 포함합니다. 두 번째 항목은 삭제 작업의 성공을 나타냅니다.

{
  "entry": [
    {
      "response": {
        "location": projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID,
        "status": "201 Created"
      }
    },
    {
      "response": {
        "status": "200 OK"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

PowerShell

번들을 실행하려면 POST 요청을 수행하고 다음 정보를 지정합니다.

  • 상위 데이터 세트와 FHIR 저장소의 이름 및 위치
  • Cloud Storage에서 번들 파일의 위치
  • 액세스 토큰

다음 샘플은 Windows PowerShell을 사용한 POST 요청을 보여줍니다.

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-RestMethod `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/fhir+json" `
  -InFile BUNDLE_FILE.json `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir" | ConvertTo-Json

개별 작업의 결과와 관계없이 배치 번들을 실행한 후 서버는 batch-response 유형 Bundle 리소스의 JSON 인코딩 표현을 반환합니다. Bundle 리소스는 요청에 입력한 내용과 해당 입력을 처리한 결과를 하나의 항목으로 포함합니다. 이 결과에는 성공 결과와 오류 결과가 혼합되어 있을 수 있습니다

트랜잭션 번들이 성공하면 서버는 요청에 입력한 내용과 성공적인 작업 결과를 하나의 항목으로 포함한 transaction-response 유형 Bundle 리소스의 JSON 인코딩 표현을 반환합니다.

트랜잭션 번들을 실행하는 동안 오류가 발생하면 응답 본문에 번들이 포함되지 않습니다. 대신 오류가 발생한 이유를 설명하는 JSON 인코딩 OperationOutcome 리소스가 포함됩니다. 롤백한 성공적인 작업은 응답에 보고되지 않습니다.

다음 샘플 번들은 위 예시를 성공적으로 실행한 출력입니다. 첫 번째 항목은 환자 만들기 작업의 성공을 나타내고 새 리소스의 ID를 포함합니다. 두 번째 항목은 삭제 작업의 성공을 나타냅니다.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-03T04:12:47.312669+00:00",
        "location": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-03T04:12:47.312669+00:00",
        "status": "200 OK"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

Go

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"

	healthcare "google.golang.org/api/healthcare/v1"
)

// fhirExecuteBundle executes an FHIR bundle.
func fhirExecuteBundle(w io.Writer, projectID, location, datasetID, fhirStoreID string) error {
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %v", err)
	}

	fhirService := healthcareService.Projects.Locations.Datasets.FhirStores.Fhir

	payload := map[string]interface{}{
		"resourceType": "Bundle",
		"type":         "transaction",
		"entry": []map[string]interface{}{
			{
				"resource": map[string]interface{}{
					"resourceType": "Patient",
					"active":       true,
				},
			},
		},
	}
	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("json.Encode: %v", err)
	}

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", projectID, location, datasetID, fhirStoreID)

	call := fhirService.ExecuteBundle(parent, bytes.NewReader(jsonPayload))
	call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8")
	resp, err := call.Do()
	if err != nil {
		return fmt.Errorf("ExecuteBundle: %v", err)
	}
	defer resp.Body.Close()

	respBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("could not read response: %v", err)
	}

	if resp.StatusCode > 299 {
		return fmt.Errorf("Create: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
	}
	fmt.Fprintf(w, "%s", respBytes)

	return nil
}

자바

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;

public class FhirStoreExecuteBundle {
  private static final String FHIR_NAME = "projects/%s/locations/%s/datasets/%s/fhirStores/%s";
  private static final JsonFactory JSON_FACTORY = new JacksonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void fhirStoreExecuteBundle(String fhirStoreName, String data)
      throws IOException, URISyntaxException {
    // String fhirStoreName =
    //    String.format(
    //        FHIR_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-fhir-id");
    // String data = "{\"resourceType\": \"Bundle\",\"type\": \"batch\",\"entry\": []}"

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();
    HttpClient httpClient = HttpClients.createDefault();
    String baseUri = String.format("%sv1/%s/fhir", client.getRootUrl(), fhirStoreName);
    URIBuilder uriBuilder = new URIBuilder(baseUri).setParameter("access_token", getAccessToken());
    StringEntity requestEntity = new StringEntity(data);

    HttpUriRequest request =
        RequestBuilder.post()
            .setUri(uriBuilder.build())
            .setEntity(requestEntity)
            .addHeader("Content-Type", "application/fhir+json")
            .addHeader("Accept-Charset", "utf-8")
            .addHeader("Accept", "application/fhir+json; charset=utf-8")
            .build();

    // Execute the request and process the results.
    HttpResponse response = httpClient.execute(request);
    HttpEntity responseEntity = response.getEntity();
    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
      System.err.print(
          String.format(
              "Exception executing FHIR bundle: %s\n", response.getStatusLine().toString()));
      responseEntity.writeTo(System.err);
      throw new RuntimeException();
    }
    System.out.print("FHIR bundle executed: ");
    responseEntity.writeTo(System.out);
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }

  private static String getAccessToken() throws IOException {
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    return credential.refreshAccessToken().getTokenValue();
  }
}

Node.js

샘플 번들 파일은 코드 샘플의 GitHub 저장소에서 사용할 수 있습니다.

const {google} = require('googleapis');
const healthcare = google.healthcare('v1');
const fs = require('fs');

async function executeFhirBundle() {
  const auth = await google.auth.getClient({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  });

  google.options({auth, headers: {'Content-Type': 'application/fhir+json'}});

  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const fhirStoreId = 'my-fhir-store';
  // const bundleFile = 'bundle.json';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/fhirStores/${fhirStoreId}`;

  const bundle = JSON.parse(fs.readFileSync(bundleFile));

  const request = {parent, requestBody: bundle};
  const resource = await healthcare.projects.locations.datasets.fhirStores.fhir.executeBundle(
    request
  );
  console.log('FHIR bundle executed');
  console.log(resource.data);
}

executeFhirBundle();

Python

샘플 번들 파일은 코드 샘플의 GitHub 저장소에서 사용할 수 있습니다.

def execute_bundle(
    base_url, project_id, cloud_region, dataset_id, fhir_store_id, bundle,
):
    """Executes the operations in the given bundle."""
    url = "{}/projects/{}/locations/{}".format(base_url, project_id, cloud_region)

    resource_path = "{}/datasets/{}/fhirStores/{}/fhir".format(
        url, dataset_id, fhir_store_id
    )

    # Make an authenticated API request
    session = get_session()

    headers = {"Content-Type": "application/fhir+json;charset=utf-8"}

    with open(bundle, "r") as bundle_file:
        bundle_file_content = bundle_file.read()

    response = session.post(resource_path, headers=headers, data=bundle_file_content)
    response.raise_for_status()

    resource = response.json()

    print("Executed bundle from file: {}".format(bundle))
    print(json.dumps(resource, indent=2))

    return resource

번들에서 생성된 리소스에 대한 참조 확인

트랜잭션 번들의 리소스는 대상 시스템에 존재하지 않지만 번들 실행 중 생성된 리소스에 대한 참조를 포함할 수 있습니다. Cloud Healthcare API는 entry.fullUrl 필드를 사용하여 리소스 간의 연결을 확인합니다. 번들에 있는 다른 리소스의 entry.fullUrl 값과 일치하는 참조는 저장소에 있는 해당 리소스의 ID로 다시 작성됩니다. 이 작업은 번들의 작업 순서에 관계없이 성공합니다.

Cloud Healthcare API에서는 다음 형식의 fullUrl이 허용됩니다.

  • urn:uuid:UUID
  • urn:oid:OID
  • 모든 URL
  • RESOURCE_TYPE/RESOURCE_ID 형식의 리소스 이름(예: Patient/123) fullUrl는 번들의 로컬 자리 표시 자이므로 이 형식을 사용하지 않는 것이 좋습니다. 이 경우 저장소의 리소스가 동일한 이름을 가지고 있지만 번들의 리소스가 만들기 작업 결과로 다른 이름으로 확인될 경우 혼동을 줄 수 있습니다.

다음 샘플 번들은 환자 리소스와 환자 리소스를 참조하는 관찰 리소스를 만듭니다.

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry":[
    {
      "request": {
        "method":"POST",
        "url":"Patient"
      },
      "fullUrl": "urn:uuid:05efabf0-4be2-4561-91ce-51548425acb9",
      "resource": {
        "resourceType":"Patient",
        "gender":"male"
      }
    },
    {
      "request": {
        "method":"POST",
        "url":"Observation"
      },
      "resource": {
        "resourceType":"Observation",
        "subject": {
          "reference": "urn:uuid:05efabf0-4be2-4561-91ce-51548425acb9"
        },
        "status":"preliminary",
        "code": {
          "text":"heart rate"
        }
      }
    }
  ]
}

다음 샘플은 번들 실행 방법을 보여줍니다.

curl

샘플 번들 파일은 코드 샘플의 GitHub 저장소에서 사용할 수 있습니다.

번들을 실행하려면 POST 요청을 수행하고 다음 정보를 지정합니다.

  • 상위 데이터 세트와 FHIR 저장소의 이름 및 위치
  • Cloud Storage에서 번들 파일의 위치
  • 액세스 토큰

다음 샘플은 curl을 사용하는 POST 요청을 보여줍니다.

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @BUNDLE_FILE.json \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir"

다음 샘플 번들은 위 예시를 성공적으로 실행한 출력입니다. 첫 번째 항목은 환자 만들기 작업의 성공을 나타내고 새 리소스의 ID를 포함합니다. 두 번째 항목은 관찰 만들기 작업의 성공을 나타내고 새 리소스의 ID를 포함합니다.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG1",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Patient/PATIENT_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Observation/OBSERVATION_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

PowerShell

샘플 번들 파일은 코드 샘플의 GitHub 저장소에서 사용할 수 있습니다.

번들을 실행하려면 POST 요청을 수행하고 다음 정보를 지정합니다.

  • 상위 데이터 세트와 FHIR 저장소의 이름 및 위치
  • Cloud Storage에서 번들 파일의 위치
  • 액세스 토큰

다음 샘플은 Windows PowerShell을 사용한 POST 요청을 보여줍니다.

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-RestMethod `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/fhir+json" `
  -InFile BUNDLE_FILE.json `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir" | ConvertTo-Json

다음 샘플 번들은 위 예시를 성공적으로 실행한 출력입니다. 첫 번째 항목은 환자 만들기 작업의 성공을 나타내고 새 리소스의 ID를 포함합니다. 두 번째 항목은 관찰 만들기 작업의 성공을 나타내고 새 리소스의 ID를 포함합니다.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG1",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Patient/PATIENT_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Observation/OBSERVATION_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}