前幾天的文章主要在認識FHIR的資料結構,包括:
我們已經知道Patient Resource可以表示病人基本資料,Observation可以表示檢驗或生命徵象,也能透過Reference建立Resource之間的關係。
但資料準備好之後,系統要如何讀取或傳送這些Resource?
這時就會使用API。
FHIR可以透過RESTful API操作Resource,例如讀取病人、搜尋檢驗結果或新增一筆就醫資料。
今天先不急著打開Postman,而是用餐廳點餐的方式理解API、REST、Client、Server、Request、Response及Endpoint。
API的全名是:
Application Programming Interface
中文通常稱為「應用程式介面」。
API提供一套規則,讓不同程式可以互相提出要求及取得結果。
例如,手機上的天氣App不一定自己在手機裡測量全世界的氣溫,而是向天氣服務的API提出Request,再取得天氣資料。
在醫療資訊情境中,也可能發生:
API就像兩套系統之間事先約定好的服務窗口。
假設我走進一家餐廳,想要點一份餐點。
正常情況下,我不會直接進入廚房翻冰箱、開瓦斯爐,也不會自己修改餐廳的訂單系統。
我會先查看菜單,告訴服務人員想點什麼,再等待廚房準備餐點。
可以將這個過程對應成API:
| 餐廳情境 | API概念 |
|---|---|
| 顧客 | Client |
| 服務人員及點餐窗口 | API |
| 菜單 | API規格或文件 |
| 廚房及餐廳系統 | Server |
| 顧客點餐 | Request |
| 餐點或回覆 | Response |
| 餐點名稱及客製需求 | Request參數 |
| 餐點售完或點餐錯誤 | 錯誤Response |
顧客不需要知道廚房內部如何管理食材,只要依照菜單及點餐規則提出要求。
同樣地,Client通常不需要直接操作Server的資料庫,而是依照API規則提出Request。
Client是提出Request的一方。
Client不一定是一台個人電腦,也可能是:
在餐廳比喻中,Client就是點餐的顧客。
在FHIR情境中,假設Postman送出請求,要求讀取一筆Patient Resource,那麼Postman就是Client。
Postman → FHIR Server
Server是接收Request並提供服務的一方。
它可能負責:
在餐廳比喻中,Server可以想成包含廚房、食材及訂單處理流程的餐廳系統。
在FHIR情境中,FHIR Server會接收Client的請求,並依照支援的功能處理Patient、Observation或其他Resource。
Request是Client傳送給Server的要求。
例如:
請給我ID為patient-001的Patient Resource。
轉換成HTTP請求,可以表示為:
GET https://hospital.example.org/fhir/Patient/patient-001
一個HTTP Request通常包含幾個重要部分:
不是每一個Request都會同時使用全部內容。
HTTP方法用來表示Client想對資料執行什麼操作。
常見方法包括:
| HTTP方法 | 常見用途 |
|---|---|
| GET | 讀取或搜尋資料 |
| POST | 建立資料 |
| PUT | 更新或建立指定位置的資料 |
| DELETE | 刪除資料 |
例如:
GET /Patient/patient-001
代表讀取Patient。
POST /Patient
可能代表建立新的Patient。
FHIR對每種HTTP方法及Resource操作有更明確的規則,會在下一篇文章詳細介紹。
URL用來告訴Client要向哪個位置提出要求。
例如:
https://hospital.example.org/fhir/Patient/patient-001
可以拆成:
| 部分 | 內容 | 用途 |
|---|---|---|
| 通訊協定 | https |
表示使用HTTPS |
| 網域 | hospital.example.org |
Server位置 |
| 基礎路徑 | /fhir |
FHIR服務位置 |
| Resource類型 | /Patient |
要操作Patient |
| Resource id | /patient-001 |
指定某筆Patient |
其中:
https://hospital.example.org/fhir
可以稱為FHIR Server的Base URL。
再加上:
/Patient/patient-001
就指向一筆特定Patient Resource。
Endpoint可以理解為API提供服務的特定位置。
例如:
https://hospital.example.org/fhir/Patient
是與Patient Resource相關的Endpoint。
https://hospital.example.org/fhir/Observation
是與Observation Resource相關的Endpoint。
加上id後:
https://hospital.example.org/fhir/Patient/patient-001
就指向特定Patient。
用餐廳比喻來說,Endpoint有點像菜單上不同的點餐項目或服務窗口。Client必須前往正確位置,Server才知道要處理哪一類資料。
Headers用來提供與Request或Response有關的附加資訊。
它們比較像包裹外面的標籤,告訴接收方如何處理內容。
FHIR API中可能使用:
Accept: application/fhir+json
表示Client希望Server回傳FHIR JSON。
如果Request Body中放入FHIR JSON,可能使用:
Content-Type: application/fhir+json
表示Client傳送的內容是FHIR JSON。
部分API也可能透過Header傳送授權資訊,例如:
Authorization: Bearer <access-token>
不過,真正的Token不能公開放在文章、截圖或程式碼中。
這兩個Header很容易混淆。
Accept: application/fhir+json
代表:
我希望收到FHIR JSON格式的Response。
Content-Type: application/fhir+json
代表:
我現在送出的Request Body是FHIR JSON格式。
可以用點餐比喻:
Accept:我希望餐點用紙盒包裝。Content-Type:我現在交給你的內容是紙本點餐單。如果是單純GET資料,通常沒有Request Body,因此不一定需要設定Content-Type;但仍可以使用Accept告訴Server希望收到的格式。
Body是Request中實際傳送的主要內容。
例如,要建立一筆Patient Resource時,Request Body可能包含:
{
"resourceType": "Patient",
"active": true,
"name": [
{
"text": "王小明"
}
]
}
Body比較常出現在POST或PUT等需要傳送資料的操作中。
GET通常用來取得資料,一般不會依賴Request Body。
用餐廳比喻來說,Body就像點餐單上的詳細內容,包括餐點、數量及客製需求。
如果不是讀取已知id的Patient,而是想搜尋符合條件的資料,可以在URL後加入查詢參數。
例如:
GET https://hospital.example.org/fhir/Patient?name=王小明
其中:
?name=王小明
就是查詢參數。
可以拆成:
?:表示後面開始進入查詢參數。name:參數名稱。王小明:參數值。=:連接參數名稱及值。如果有多個條件,可以使用&連接:
GET https://hospital.example.org/fhir/Patient?name=王小明&birthdate=2000-01-01
表示同時使用姓名及出生日期搜尋Patient。
實際支援哪些搜尋參數,必須查看FHIR規範及該FHIR Server的CapabilityStatement。
Response是Server處理Request後傳回Client的結果。
假設Client送出:
GET https://hospital.example.org/fhir/Patient/patient-001
Server可能回傳:
{
"resourceType": "Patient",
"id": "patient-001",
"active": true,
"name": [
{
"text": "王小明"
}
]
}
這份Patient Resource就是Response Body的一部分。
一個HTTP Response通常包含:
Status Code是由三位數字組成的HTTP狀態碼,用來表示Request的處理結果。
常見狀態包括:
| Status Code | 常見意義 |
|---|---|
| 200 OK | Request成功 |
| 201 Created | 資料建立成功 |
| 400 Bad Request | Request內容有問題 |
| 401 Unauthorized | 尚未完成有效身分驗證 |
| 403 Forbidden | 已辨識身分但沒有權限 |
| 404 Not Found | 找不到指定Resource |
| 500 Internal Server Error | Server內部發生錯誤 |
例如,讀取存在的Patient可能得到:
200 OK
讀取不存在的Patient可能得到:
404 Not Found
狀態碼只能先提供大方向,實際錯誤原因仍可能出現在Response Body中。
FHIR Server發生錯誤時,可能回傳OperationOutcome Resource,提供更詳細的問題說明。
假設我們想讀取王小明的Patient Resource。
GET https://hospital.example.org/fhir/Patient/patient-001
Accept: application/fhir+json
FHIR Server收到要求後,確認:
patient-001是否存在如果成功,可能回傳:
200 OK
Content-Type: application/fhir+json
Response Body:
{
"resourceType": "Patient",
"id": "patient-001",
"active": true,
"name": [
{
"text": "王小明"
}
]
}
Postman可以直接顯示JSON;App則可以將姓名及其他資料呈現在使用者介面上。
整個流程可以簡化成:
Client送出Request
↓
FHIR Server處理
↓
Server回傳Response
↓
Client顯示或使用資料
API是一個較廣泛的概念,代表應用程式之間互動的介面。
REST則是一種軟體架構風格,完整名稱是:
Representational State Transfer
符合REST設計概念的API,通常稱為RESTful API。
REST經常使用HTTP,並將要操作的內容視為Resource。
例如:
/Patient
代表Patient這類Resource。
/Patient/patient-001
代表一筆特定Patient Resource。
再搭配HTTP方法表達操作:
GET /Patient/patient-001
讀取Patient。
DELETE /Patient/patient-001
要求刪除Patient。
所以API不一定都是REST API,而REST API是API的一種設計方式。
FHIR定義了一套RESTful API,讓Client可以對Resource執行不同互動。
例如:
| 需求 | FHIR API示意 |
|---|---|
| 讀取Patient | GET /Patient/patient-001 |
| 搜尋Patient | GET /Patient?name=王小明 |
| 建立Patient | POST /Patient |
| 更新Patient | PUT /Patient/patient-001 |
| 刪除Patient | DELETE /Patient/patient-001 |
| 查看Server能力 | GET /metadata |
FHIR使用Resource作為資料模型,再透過HTTP及RESTful API提供操作方式。
不過,FHIR Server不一定支援所有Resource及操作。實際支援內容應查看Server提供的CapabilityStatement。
Client呼叫:
GET /Patient/patient-001
不代表Client直接進入Server的資料庫。
FHIR Server可能在背後:
Client只需要依照API規格提出要求,不需要知道Server內部如何完成。
這就像顧客只依照菜單點餐,不需要進入廚房了解廚師如何取得及處理每一種食材。
顧客如果沒有菜單,就不知道餐廳提供哪些餐點。
同樣地,開發者需要API文件才能知道:
在FHIR中,除了官方規範外,每台FHIR Server也應該透過CapabilityStatement說明自己支援的Resource、操作及功能。
因此,不能只知道對方是FHIR Server,就假設所有Server的功能都完全相同。
如果FHIR Server保存真實醫療資料,不能讓所有人只知道網址就自由查詢。
正式環境通常還需要處理:
本系列後續實作會使用公開測試伺服器及完全虛構的資料。
公開測試Server可能不要求登入,但這不代表正式醫療系統也能在沒有驗證的情況下提供資料。
| API概念 | 餐廳情境 | FHIR情境 |
|---|---|---|
| Client | 顧客 | Postman或醫療App |
| Server | 餐廳及廚房 | FHIR Server |
| API | 點餐服務 | FHIR API |
| API文件 | 菜單及點餐規則 | FHIR規範及CapabilityStatement |
| Endpoint | 指定點餐項目或窗口 | /Patient |
| Request | 顧客提出點餐 | GET /Patient/123 |
| Header | 包裝、身分或格式要求 | Accept: application/fhir+json |
| Body | 點餐的詳細內容 | Patient JSON |
| Response | 餐點或店員回覆 | FHIR Resource或錯誤資訊 |
| Status Code | 成功、售完或無法供應 | 200、404或其他狀態碼 |
請觀察以下Request:
GET https://hospital.example.org/fhir/Observation?patient=patient-001
Accept: application/fhir+json
可以找出:
https://hospital.example.org/fhir
Observation
patient=patient-001
Accept: application/fhir+json
整個Request的意思是:
請搜尋與patient-001這位病人有關的Observation,並以FHIR JSON格式回傳。
今天使用餐廳點餐的比喻,認識了REST API的基本概念。
FHIR以Resource作為資料模型,並提供RESTful API操作Patient、Observation及其他Resource。
今天先建立API溝通的整體概念,下一篇將更詳細地比較GET、POST、PUT及DELETE,並認識常見HTTP狀態碼及FHIR的OperationOutcome。
Day 14|GET、POST、PUT、DELETE有什麼不同?
HL7 FHIR R4:RESTful API
https://hl7.org/fhir/R4/http.html
HL7 FHIR R4:FHIR Overview for Developers
https://hl7.org/fhir/R4/overview-dev.html
HL7 FHIR R4:CapabilityStatement
https://hl7.org/fhir/R4/capabilitystatement.html
HL7 FHIR R4:OperationOutcome
https://hl7.org/fhir/R4/operationoutcome.html
RFC 9110:HTTP Semantics
https://www.rfc-editor.org/rfc/rfc9110