Gérer les ressources FHIR à l'aide des groupes FHIR

Cette page explique comment gérer les ressources FHIR en exécutant des groupes FHIR, qui rassemblent une collection de ressources FHIR et d'opérations à effectuer sur ces ressources.

La méthode ExecuteBundle met en œuvre l'interaction standard FHIR par lot/transaction (DSTU2, STU3 et R4).

Groupes FHIR

Un groupe FHIR contient un tableau d'entrées, chacune représentant une opération, par exemple, la création, la mise à jour ou la suppression sur une ressource, telle qu'une observation ou un patient. Consultez les descriptions détaillées des éléments de la ressource groupée.

Lorsque vous exécutez un groupe FHIR, le type de groupe détermine comment les opérations du groupe sont effectuées. Les types de groupes suivants sont disponibles :

  • batch : exécute les opérations en tant que plusieurs requêtes indépendantes.
  • transaction : exécute les opérations en tant que plusieurs requêtes interdépendantes.

Par exemple, supposons qu'un groupe de transactions comprenne la création d'une ressource Patient et d'une ressource Observation. Si la requête de création de ressource Patient échoue, la ressource Observation n'est pas créée.

Si une opération échoue lorsque le type de groupe est batch, l'API Cloud Healthcare exécute les opérations restantes dans le groupe. Si une opération échoue lorsque le type de groupe est transaction, l'API Cloud Healthcare arrête l'exécution des opérations et effectue un rollback de la transaction.

Accorder des autorisations pour exécuter des groupes

Le rôle d'autorisation datasets.fhirStores.fhir.executeBundle est requis pour exécuter des groupes. Pour accorder cette autorisation, utilisez le rôle healthcare.fhirResourceReader. Pour connaître les étapes à suivre pour accorder cette autorisation, consultez la section Modifier une stratégie.

L'API Cloud Healthcare vérifie les autorisations pour chaque opération du groupe. Si vous disposez de l'autorisation healthcare.fhirResources.create, mais pas de l'autorisation healthcare.fhirResources.update, vous ne pouvez exécuter que des groupes contenant des opérations healthcare.fhirResources.create.

Exécuter un groupe

Pour exécuter un groupe FHIR, utilisez la méthode projects.locations.datasets.fhirStores.fhir.executeBundle.

Dans les exemples suivants, BUNDLE.Json correspond au chemin d'accès et au nom de fichier d'un groupe FHIR encodé au format JSON. Vous pouvez également inclure le groupe dans le corps de la requête.

L'exemple de groupe suivant crée une ressource Patient et en supprime une autre :

{
  "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"
      }
    }
  ]
}

Les exemples suivants montrent comment exécuter un groupe.

curl

Pour exécuter un groupe, exécutez une requête POST et spécifiez les informations suivantes :

  • Le nom et l'emplacement de l'ensemble de données parent et du magasin FHIR
  • L'emplacement du fichier de groupe sur votre ordinateur local
  • Un jeton d'accès

L'exemple suivant montre une requête POST utilisant curl :

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"

Quel que soit le résultat des opérations individuelles, après l'exécution d'un groupe par lot, le serveur renvoie une représentation encodée au format JSON d'une ressource Bundle de type batch-response. La ressource Bundle contient une entrée pour chaque entrée de la requête et le résultat du traitement de l'entrée, pouvant se composer d'une combinaison de résultats de réussite et d'erreur.

Si un lot de transactions réussit, le serveur renvoie une représentation encodée au format JSON d'une ressource Bundle de type transaction-response contenant une entrée pour chaque entrée de la requête avec le résultat de réussite de l'opération.

Si une erreur se produit lors de l'exécution d'un groupe de transactions, le corps de la réponse ne contient aucun groupe. Au lieu de cela, il contient une ressource OperationOutcome encodée au format JSON décrivant la raison de l'erreur. Les opérations réussies qui ont été annulées ne sont pas signalées dans la réponse.

L'exemple de groupe suivant correspond au résultat de l'exécution de l'exemple ci-dessus. La première entrée indique la réussite de l'opération de création d'un Patient et inclut l'ID de la nouvelle ressource. La deuxième entrée indique la réussite de l'opération de suppression.

{
  "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

Pour exécuter un groupe, exécutez une requête POST et spécifiez les informations suivantes :

  • Le nom et l'emplacement de l'ensemble de données parent et du magasin FHIR
  • L'emplacement du fichier de groupe sur votre ordinateur local
  • Un jeton d'accès

L'exemple suivant montre une requête POST utilisant Windows PowerShell :

$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

Quel que soit le résultat des opérations individuelles, après l'exécution d'un groupe par lot, le serveur renvoie une représentation encodée au format JSON d'une ressource Bundle de type batch-response. La ressource Bundle contient une entrée pour chaque entrée de la requête et le résultat du traitement de l'entrée, pouvant se composer d'une combinaison de résultats de réussite et d'erreur.

Si un lot de transactions réussit, le serveur renvoie une représentation encodée au format JSON d'une ressource Bundle de type transaction-response contenant une entrée pour chaque entrée de la requête avec le résultat de réussite de l'opération.

Si une erreur se produit lors de l'exécution d'un groupe de transactions, le corps de la réponse ne contient aucun groupe. Au lieu de cela, il contient une ressource OperationOutcome encodée au format JSON décrivant la raison de l'erreur. Les opérations réussies qui ont été annulées ne sont pas signalées dans la réponse.

L'exemple de groupe suivant correspond au résultat de l'exécution de l'exemple ci-dessus. La première entrée indique la réussite de l'opération de création d'un Patient et inclut l'ID de la nouvelle ressource. La deuxième entrée indique la réussite de l'opération de suppression.

{
  "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,
				},
				"request": map[string]interface{}{
					"method": "POST",
					"url":    "Patient",
				},
			},
		},
	}
	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
}

Java

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

Un exemple de fichier de groupe est disponible dans le dépôt GitHub de l'exemple de code.

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
  headers: {'Content-Type': 'application/fhir+json'},
});
const fs = require('fs');

async function executeFhirBundle() {
  // 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

Un exemple de fichier de groupe est disponible dans le dépôt GitHub de l'exemple de code.

def execute_bundle(
    project_id,
    location,
    dataset_id,
    fhir_store_id,
    bundle,
):
    """Executes the operations in the given bundle.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/healthcare/api-client/v1/fhir
    before running the sample."""
    # Imports Python's built-in "os" module
    import os

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using a service account
    from google.oauth2 import service_account

    # Gets credentials from the environment.
    credentials = service_account.Credentials.from_service_account_file(
        os.environ["GOOGLE_APPLICATION_CREDENTIALS"]
    )
    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # fhir_store_id = 'my-fhir-store' # replace with the FHIR store ID
    # bundle = 'bundle.json'  # replace with the bundle file
    url = "{}/projects/{}/locations/{}".format(base_url, project_id, location)

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

    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

Résoudre les références aux ressources créées dans un groupe

Les ressources d'un groupe de transactions peuvent contenir des références à des ressources qui n'existent pas dans le système cible, mais qui sont créées lors de l'exécution du groupe. L'API Cloud Healthcare résout l'association entre les ressources à l'aide du champ entry.fullUrl. Les références qui correspondent à la valeur entry.fullUrl d'une autre ressource du groupe sont réécrites dans l'ID de la ressource correspondante dans le magasin. Cette opération réussit quel que soit l'ordre des opérations dans le groupe.

L'API Cloud Healthcare accepte fullUrl aux formats suivants :

  • urn:uuid:UUID
  • urn:oid:OID
  • N'importe quelle URL
  • Un nom dla ressource au format RESOURCE_TYPE/RESOURCE_ID, par exemple Patient/123. Il n'est pas recommandé d'utiliser ce format, car fullUrl est un espace réservé local au groupe. Cela peut prêter à confusion si une ressource du magasin porte le même nom, mais que la ressource du groupe est associée à un nom différent suite à une opération de création.

L'exemple de groupe suivant crée une ressource Patient et une ressource Observation faisant référence à cette ressource Patient.

{
  "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"
        }
      }
    }
  ]
}

Les exemples suivants montrent comment exécuter un groupe.

curl

Un exemple de fichier de groupe est disponible dans le dépôt GitHub de l'exemple de code.

Pour exécuter un groupe, exécutez une requête POST et spécifiez les informations suivantes :

  • Le nom et l'emplacement de l'ensemble de données parent et du magasin FHIR
  • L'emplacement du fichier de groupe dans Cloud Storage
  • Un jeton d'accès

L'exemple suivant montre une requête POST utilisant curl :

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"

L'exemple de groupe suivant correspond au résultat de l'exécution de l'exemple ci-dessus. La première entrée indique la réussite de l'opération de création d'un Patient et inclut l'ID de la nouvelle ressource. La deuxième entrée indique que l'opération a réussi à créer l'observation et inclut l'ID de la nouvelle ressource.

{
  "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

Un exemple de fichier de groupe est disponible dans le dépôt GitHub de l'exemple de code.

Pour exécuter un groupe, exécutez une requête POST et spécifiez les informations suivantes :

  • Le nom et l'emplacement de l'ensemble de données parent et du magasin FHIR
  • L'emplacement du fichier de groupe dans Cloud Storage
  • Un jeton d'accès

L'exemple suivant montre une requête POST utilisant Windows PowerShell :

$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

L'exemple de groupe suivant correspond au résultat de l'exécution de l'exemple ci-dessus. La première entrée indique la réussite de l'opération de création d'un Patient et inclut l'ID de la nouvelle ressource. La deuxième entrée indique que l'opération a réussi à créer l'observation et inclut l'ID de la nouvelle ressource.

{
  "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"
}