Lesson 0010 · FHIR · Modul REST · ±30 menit

Bundle transaction vs batch, conditional create; versioning, ETag, If-Match

Banyak perubahan dalam satu request, dan optimistic locking lewat header HTTP

Target lesson ini: Anda bisa menjawab "Apa beda Bundle transaction dan batch? Bagaimana mencegah dua user saling menimpa update?" tanpa catatan: aturan atomik transaction, conditional create untuk import idempoten, dan optimistic locking dengan ETag + If-Match → 412.

Sebelumnya, di lesson 0009, Anda membaca Bundle searchset hasil search. Lesson ini memakai Bundle ke arah sebaliknya, untuk mengirim banyak perubahan sekaligus, lalu membahas cara mencegah dua user saling menimpa update.

1. Bundle transaction vs batch, dan conditional create

Keduanya dikirim dengan POST [base]; body-nya Bundle dengan type transaction atau batch, dan setiap entry membawa request.method + request.url ("the HTTP action to be executed for this entry"). [1] [2]

transactionbatch
Definisi"processed by a server as an atomic commit""a group of independent actions"
Satu entry gagalsemua ditolak: "either succeed or fail together"; status 4xx/5xx + satu OperationOutcomeentry lain tetap diproses; "The success or failure of one change SHOULD not alter" yang lain
Respons sukses200 + transaction-response, semua entry sukses200 + batch-response, "some responses may indicate failure and others success"
Referensi antar-entryboleh, lewat fullUrl sementara (urn:uuid:…); server mengganti dengan id barutidak boleh: "there SHALL be no interdependencies between the different entries"
Padanan JSprisma.$transaction([...])Promise.allSettled([...])

Kutipan: [1] [3]. Untuk transaction, server "SHALL also update any references to that resource in the same bundle". Padanan JS hanya analogi perilaku, bukan klaim spesifikasi.

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry": [
    { "fullUrl": "urn:uuid:61ebe359-bfdc-4613-8bf2-c5e300945f0a",
      "resource": { "resourceType": "Patient",
                    "identifier": [{ "system": "http://rs.example.org/mrn", "value": "12345" }] },
      "request": { "method": "POST", "url": "Patient",
                   "ifNoneExist": "identifier=http://rs.example.org/mrn|12345" } },
    { "resource": { "resourceType": "Observation", "status": "final",
                    "code": { "coding": [{ "system": "http://loinc.org", "code": "29463-7" }] },
                    "subject": { "reference": "urn:uuid:61ebe359-bfdc-4613-8bf2-c5e300945f0a" } },
      "request": { "method": "POST", "url": "Observation" } }
  ]
}

Conditional create (Bundle.entry.request.ifNoneExist: "Instruct the server not to perform the create if a specified resource already exists" [4], atau header If-None-Exist: [search parameters] pada POST biasa) membuat import idempoten: 0 cocok → create biasa; 1 cocok → "The server ignores the post and returns 200 OK"; lebih dari 1 → 412, kriteria "not selective enough". Pola sama untuk conditional update PUT [base]/Patient?identifier=…. [1] Di contoh atas, kalau Patient MRN 12345 sudah ada, Observation tetap dibuat dan merujuk ke Patient lama.

Urutan pemrosesan transaction

"The outcome of processing the transaction SHALL NOT depend on the order of the resources in the transaction": server sendiri yang mengurutkan berdasarkan method. [1] Susun urutan yang ditetapkan spesifikasi:

  1. Proses semua DELETE
  2. Proses semua POST (create)
  3. Proses semua PUT atau PATCH
  4. Proses semua GET atau HEAD

DELETE → POST → PUT/PATCH → GET/HEAD. Jadi GET/HEAD diproses setelah semua perubahan di Bundle yang sama, dan urutan entry yang Anda tulis tidak mengubah hasil. [1]

Latihan 1 — Transaction, batch, conditional create

2. Versioning: vread, ETag, If-Match

// Optimistic locking: sama seperti kolom version di ORM, tapi lewat header HTTP
const read = await fetch(`${base}/Patient/123`, { headers: { Accept: "application/fhir+json" } });
const etag = read.headers.get("ETag");          // W/"3"
const patient = await read.json();
patient.telecom = [{ system: "phone", value: "+62-812-0000-0000" }];

const res = await fetch(`${base}/Patient/123`, {
  method: "PUT",
  headers: { "Content-Type": "application/fhir+json", "If-Match": etag! },
  body: JSON.stringify(patient),
});
if (res.status === 412) {
  // user lain sudah menyimpan versi 4: muat ulang, tampilkan perbedaan, minta user memutuskan
}

Melihat versi lama: GET [base]/Patient/123/_history/3 (vread, satu versi) atau GET [base]/Patient/123/_history (Bundle type: "history", semua versi). [1]

Latihan 2 — Versi dan konflik

Latihan 3 — Ucapkan dengan suara keras (±10 menit)

Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.

"Apa beda Bundle transaction dan batch? Kapan Anda memakai masing-masing?" (60–90 detik)
Bandingkan dengan jawaban contoh dan checklist-nya

"Keduanya di-POST ke base URL, POST [base], berisi Bundle dengan type transaction atau batch; setiap entry punya request.method dan request.url, jadi satu Bundle bisa berisi create, update, delete, dan read. Transaction itu atomik: semua sukses atau semua gagal. Kalau satu entry gagal, server menolak semuanya dengan 4xx/5xx dan satu OperationOutcome; kalau sukses, saya dapat transaction-response. Di dalam transaction, entry boleh saling merujuk lewat fullUrl urn:uuid:..., dan server mengganti referensi itu dengan id asli. Batch adalah sekumpulan aksi independen: tidak boleh ada ketergantungan antar-entry, dan batch-response berisi status per entry, sebagian bisa gagal. Saya pakai transaction untuk data yang harus konsisten, misalnya Encounter beserta Observation-nya; batch untuk banyak aksi yang tidak saling terkait, misalnya beberapa read sekaligus. Padanan JS: transaction seperti prisma.$transaction, batch seperti Promise.allSettled. Untuk import yang idempoten saya tambahkan conditional create (ifNoneExist) per entry."

Checklist: apakah Anda menyebut…

  • POST [base] dengan Bundle.type transaction/batch, entry berisi request.method + url?
  • transaction atomik: semua sukses atau semua gagal (4xx/5xx + OperationOutcome)?
  • batch: aksi independen, respons per entry, sebagian boleh gagal?
  • referensi antar-entry lewat fullUrl urn:uuid hanya di transaction; batch tanpa ketergantungan?
  • contoh kapan memakai masing-masing (data saling terkait vs aksi lepas)?
  • padanan JS ($transaction vs allSettled) atau conditional create untuk idempoten?

Lulus kalau minimal 4 dari 6.

"Dua user mengedit Patient yang sama di React app Anda. Bagaimana mencegah lost update, dan bagaimana error-nya sampai ke UI?" (90 detik)
Bandingkan dengan jawaban contoh dan checklist-nya

"Setiap resource FHIR punya meta.versionId, dan server mengirimnya sebagai ETag, misalnya W/"3". Saat read saya simpan ETag itu; saat update saya kirim PUT [base]/Patient/123 dengan If-Match: W/"3". Kalau sementara itu user lain sudah menyimpan versi 4, server menolak dengan 412 Precondition Failed, bukan menimpa diam-diam. Ini optimistic locking, sama seperti kolom version di ORM. Di UI saya tangkap 412, ambil versi terbaru, lalu minta user meninjau atau menggabungkan perubahan. Versi lama bisa dilihat dengan vread _history/3 atau seluruh history. Untuk error lain, server mengembalikan OperationOutcome: setiap issue punya severity, code seperti required atau conflict, diagnostics, dan expression yang menunjuk elemen yang salah, jadi saya bisa menandai field di form. 400 artinya body tidak valid secara dasar, 422 melanggar profil atau aturan bisnis."

Checklist: apakah Anda menyebut…

  • meta.versionId = ETag (weak, W/"n")?
  • update dengan If-Match berisi ETag terakhir yang dibaca?
  • versi tidak cocok → 412 Precondition Failed, lalu reload/merge di UI?
  • menyebut optimistic locking / lost update, dan vread atau _history?
  • error dalam OperationOutcome: severity, code, diagnostics, expression?
  • membedakan 400 (tidak valid dasar) dan 422 (profil/aturan bisnis)?

Lulus kalau minimal 4 dari 6. Review 0021 akan menanyakan salah satunya lagi.

Ringkasan untuk dicetak: Reference: REST, search, Bundle · Istilah: GLOSSARY.md (bagian "REST API")

Ada yang belum jelas? Tanyakan ke saya (agent) di terminal.

Sumber (semua FHIR R4 4.0.1, diakses 2026-10-26): [1] RESTful API · [2] Bundle · [3] CodeSystem BundleType · [4] Bundle: detailed definitions