Bundle — Conteneur multi-ressources
L'enveloppe qui permet à FHIR de transporter plusieurs ressources d'un coup : résultat de recherche, transaction atomique, document patient, message technique.
Objet de la ressource
Bundle répond à une question simple : comment transmettre dix ressources FHIR dans une seule charge utile ? Que ce soit le résultat d'une search qui retourne quinze Patients, une transaction atomique qui crée un Patient et ses Observations associées, ou un document IPS qui consolide le résumé patient, Bundle est l'enveloppe universelle.
La discriminance se fait via le champ type, qui prend l'une des huit
valeurs autorisées et dicte la sémantique du Bundle (avec ou sans transaction,
persistance ou transit, paged ou non).
Les huit types de Bundle
| Type | Usage | Persistance | Sémantique transactionnelle |
|---|---|---|---|
document | Document FHIR (résumé patient IPS, CDA-like) | Oui — peut être stocké | Pas de transaction. Première entry = Composition. |
message | Échange asynchrone style HL7 v2 | Transitoire | Pas de transaction. Première entry = MessageHeader. |
transaction | Création / mise à jour multi-ressources atomique | Transitoire | Atomique — toutes les entries ou aucune. Le serveur résout les références. |
transaction-response | Réponse du serveur à une transaction | Transitoire | Liste des résultats individuels. |
batch | Opérations multi-ressources indépendantes | Transitoire | Non atomique — chaque entry réussit ou échoue indépendamment. |
batch-response | Réponse du serveur à un batch | Transitoire | Liste des résultats individuels. |
history | Historique d'une ressource ou du serveur | Transitoire | Pas de transaction. |
searchset | Résultat d'une recherche | Transitoire | Inclut pagination via link. |
collection | Sac générique de ressources, libre d'interprétation | Variable | Pas de sémantique technique imposée. |
subscription-notification | Notification d'une Subscription R5 | Transitoire | Mécanisme push temps réel. |
Champs clés
| Champ | Type | Cardinalité | Rôle |
|---|---|---|---|
type | code | 1..1 | Mandatoire. L'un des huit (dix avec subscription-notification) types listés ci-dessus. |
timestamp | instant | 0..1 | Date/heure de génération du Bundle. |
total | unsignedInt | 0..1 | Total de matches pour un searchset (peut différer de entry.length en mode paginé). |
link | BackboneElement[] | 0..* | Liens de pagination : self, next, previous, first, last. |
entry | BackboneElement[] | 0..* | Liste des ressources transportées. Chaque entry contient : |
entry.fullUrl | uri | 0..1 | URL absolue de la ressource. Critique pour la résolution des références. |
entry.resource | Resource | 0..1 | La ressource elle-même. |
entry.search | BackboneElement | 0..1 | Pour searchset : mode (match, include, outcome) et score. |
entry.request | BackboneElement | 0..1 | Pour transaction / batch : méthode HTTP (POST, PUT, DELETE, PATCH, GET) et URL. |
entry.response | BackboneElement | 0..1 | Pour transaction-response / batch-response : status, location, etag, lastModified, outcome. |
signature | Signature | 0..1 | Signature électronique du Bundle (pour les types document et message notamment). |
Exemple — transaction
Bundle transaction qui crée un nouveau Patient, met à jour un Patient
existant (PUT idempotent), et crée une Observation poids rattachée au premier
Patient via une référence relative à urn:uuid: :
{
"resourceType": "Bundle",
"id": "bundle-transaction",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:88f151c0-a954-468a-88bd-5ae15c08e059",
"resource": {
"resourceType": "Patient",
"identifier": [{
"system": "http:/example.org/fhir/ids",
"value": "234234"
}],
"active": true,
"name": [{ "family": "Chalmers", "given": ["Peter", "James"] }],
"gender": "male",
"birthDate": "1974-12-25"
},
"request": {
"method": "POST",
"url": "Patient"
}
},
{
"fullUrl": "http://example.org/fhir/Patient/123",
"resource": {
"resourceType": "Patient",
"id": "123",
"active": true,
"name": [{ "family": "Smith", "given": ["John"] }],
"gender": "male",
"birthDate": "1980-01-15"
},
"request": {
"method": "PUT",
"url": "Patient/123"
}
},
{
"fullUrl": "urn:uuid:79378cb8-8f72-4d33-8b8d-d6c0c5e1e3bb",
"resource": {
"resourceType": "Observation",
"status": "final",
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "29463-7",
"display": "Body Weight"
}]
},
"subject": {
"reference": "urn:uuid:88f151c0-a954-468a-88bd-5ae15c08e059"
},
"effectiveDateTime": "2026-05-14T10:30:00Z",
"valueQuantity": {
"value": 72.5,
"unit": "kg",
"system": "http://unitsofmeasure.org",
"code": "kg"
}
},
"request": {
"method": "POST",
"url": "Observation"
}
}
]
} À retenir :
-
fullUrlenurn:uuid:: pour les ressources créées dans le bundle (pas encore d'idcôté serveur), on utilise un UUID temporaire. Le serveur le remplace par l'idalloué et résout les références internes. - Référence interne :
Observation.subject.reference = "urn:uuid:88f151c0..."pointe vers le Patient qui sera créé dans la même transaction. Le serveur garantit la cohérence référentielle. - Méthode HTTP par entry :
POSTpour les créations (id alloué par le serveur),PUTpour les mises à jour idempotentes (id fourni par le client). - Atomicité : si la moindre entry échoue, le serveur ROLLBACK l'intégralité. Aucun changement partiel.
Exemple — searchset
Bundle searchset retourné par GET /Patient?family=Chalmers
avec deux matches et un lien next pour la pagination :
{
"resourceType": "Bundle",
"id": "searchset-example",
"type": "searchset",
"total": 2,
"link": [
{
"relation": "self",
"url": "https://server/Patient?family=Chalmers"
},
{
"relation": "next",
"url": "https://server/Patient?family=Chalmers&_page=2"
}
],
"entry": [
{
"fullUrl": "https://server/Patient/example",
"resource": {
"resourceType": "Patient",
"id": "example",
"name": [{ "family": "Chalmers", "given": ["Peter"] }],
"gender": "male",
"birthDate": "1974-12-25"
},
"search": {
"mode": "match",
"score": 1.0
}
},
{
"fullUrl": "https://server/Patient/example2",
"resource": {
"resourceType": "Patient",
"id": "example2",
"name": [{ "family": "Chalmers", "given": ["Sarah"] }],
"gender": "female",
"birthDate": "1981-07-12"
},
"search": {
"mode": "match",
"score": 0.95
}
}
]
} total: deux matches (peut être omis si serveur en mode count-disabled).link[self]: la requête originale.link[next]: page suivante dans le jeu de résultats. Le client suit ces liens pour paginer.entry.search.mode:match= ressource qui correspond au critère ;include= ressource incluse via_includemais pas dans le critère initial.entry.search.score: pertinence du match (0..1), uniquement quand le serveur supporte le ranking sémantique.
Transaction vs batch
Les deux types transaction et batch partagent la même
structure mais ont une sémantique différente :
| Aspect | transaction | batch |
|---|---|---|
| Atomicité | Oui — tout ou rien | Non — chaque entry indépendante |
| Résolution de référence | Le serveur résout les urn:uuid: et les références circulaires entre entries | Pas de résolution — chaque entry comme une requête isolée |
| Ordre d'exécution | Côté serveur, déterministe (DELETE, POST, PUT/PATCH, GET, HEAD) | Ordre des entries |
| Cas d'usage | Création d'un dossier patient complet, post-test génétique avec ses observations | Bulk update indépendant : rafraîchir 50 Observations isolément |
| Performance | Plus coûteux côté serveur (verrou + rollback) | Moins coûteux, paralélisable |
Pièges courants
-
fullUrlmanquant ou non-URN : dans une transaction, unfullUrlmanquant rend impossible la résolution des références internes. Toujours fournir, soit enurn:uuid:<UUID>, soit en URL absolue. - Référence absolue dans une transaction : si l'
Observationréférence le Patient via"reference": "http://server/Patient/123"alors que le Patient est créé dans le même bundle sans cet ID, la transaction échoue. - Confusion
searchsetetcollection: certains serveurs anciens retournentcollectionau lieu desearchset— le client doit traiter les deux. Ne pas confondre côté création. - Bundle > 100 entries : la spec ne fixe pas de limite, mais la plupart des serveurs imposent un plafond (HAPI : 1000 par défaut, Epic R5 : 100). Pagination obligatoire au-delà.
- Bundle document non signé : un document IPS sans
signaturepeut être refusé par certaines autorités nationales (FR, NL, UK). Toujours inclure pour les documents persistants. -
type=transactionen POST sur/Patient: un Bundle de type transaction se poste à la racine du serveur (POST /), pas sur l'URL d'une ressource individuelle.
Ressources liées
- Composition — première entry obligatoire d'un Bundle
document. - MessageHeader — première entry obligatoire d'un Bundle
message. - OperationOutcome — porte les erreurs et warnings dans les réponses transaction / batch.
- Subscription et SubscriptionStatus — déclenchent les notifications de type
subscription-notification. - Parameters — alternative à Bundle pour les paramètres d'opération (
$everything,$lastn…).
Voir aussi : Patient — la ressource démographique fondamentale, le hub FHIR R5, et l'équivalent fonctionnel d'un Bundle multi-ORM en v2 : ORM^O01.