ข้ามไปยังเนื้อหา
กลับไปหน้าบทความ
การพัฒนาซอฟต์แวร์

RPC กับ REST แตกต่างกันอย่างไร และควรเลือกใช้แบบไหน

RPC เรียก procedure บน server ด้วยชื่อและพารามิเตอร์ ส่วน REST ส่งคำขอไปที่ resource ผ่าน interface กลางของ HTTP เทียบทีละประเด็นพร้อมตัวอย่าง gRPC และ JSON-RPC อิงจาก Fielding, RFC 9110 และ spec ต้นทาง

เริ่มต้น10 นาทีในการอ่าน

ในบทความนี้

ตอบสั้น ๆ ก่อนครับ RPC คือการเรียก procedure บนเครื่องอื่นโดยระบุชื่อ procedure และส่งพารามิเตอร์ไป แล้วรอผลตอบกลับ ส่วน REST คือการส่งคำขอไปที่ resource ที่มี URL ของตัวเอง โดยใช้ method มาตรฐานของ HTTP ที่ทุก resource ใช้ร่วมกัน เช่น GET และ DELETE

สองแบบนี้ใช้ HTTP ได้ทั้งคู่ ความต่างจึงไม่ได้อยู่ที่ protocol แต่อยู่ที่ว่า request ชี้ไปที่อะไร ถ้าชี้ไปที่ฟังก์ชัน เช่น GetUser คือ RPC ถ้าชี้ไปที่ข้อมูล เช่น /users/42 และให้ method บอกว่าจะทำอะไรกับมัน คือแนวทางของ REST ครับ

ค้นข้อมูลผู้ใช้คนเดียวกันสองแบบ RPC เรียก procedure ชื่อ GetUser และส่ง id ไปใน body ส่วน REST ส่ง GET ไปที่ resource /users/42

RPC คืออะไร

RPC ย่อมาจาก Remote Procedure Call คือการเรียกฟังก์ชันที่อยู่บนเครื่องอื่นให้ดูเหมือนเรียกฟังก์ชันในโปรแกรมของเราเอง Roy Fielding อธิบายลักษณะของ RPC ไว้ว่า protocol ระบุ procedure และส่งชุดพารามิเตอร์ที่กำหนดไว้ให้ แล้วรอคำตอบ (หัวข้อ 6.5.2)

RFC 5531 ซึ่งเป็น spec ของ ONC RPC version 2 อธิบายขั้นตอนไว้แบบเดียวกัน ฝั่งที่เรียกส่ง call message ไปที่ server แล้วรอ (block) จนกว่าจะได้ reply message RFC ฉบับเดียวกันยังเตือนว่าการเรียกข้ามเครือข่ายไม่เหมือนการเรียกในเครื่องจริง ๆ ต้องจัดการ error แบบที่ฟังก์ชันในเครื่องไม่มี ไม่มี global variable ร่วมกัน ต้องยืนยันตัวตน และ remote procedure มักช้ากว่า local procedure ตั้งแต่สิบเท่าขึ้นไป (one or more orders of magnitude)

ใน gRPC ซึ่งเป็น RPC framework ที่ใช้กันมากในปัจจุบัน ฝั่ง client เรียก method ผ่าน stub ที่ generate ขึ้นมา เหมือนเรียก object ในเครื่องตัวเอง ทั้งที่ method นั้นรันอยู่บนอีกเครื่อง

REST คืออะไร

REST ย่อมาจาก Representational State Transfer เป็นรูปแบบสถาปัตยกรรม (architectural style) ที่ Roy Fielding เสนอไว้ในบทที่ 5 ของวิทยานิพนธ์ปริญญาเอกที่ University of California, Irvine ปี 2000 สำหรับระบบ hypermedia แบบกระจาย REST ไม่ใช่ protocol และไม่ใช่ library แต่เป็นชุดข้อจำกัด (constraint) ที่ระบบต้องทำตาม

หน่วยหลักของ REST คือ resource Fielding นิยามว่าข้อมูลอะไรก็ตามที่ตั้งชื่อได้ เป็น resource ได้ เช่น ผู้ใช้คนหนึ่ง เอกสารหนึ่งฉบับ หรือรายการคำสั่งซื้อ สิ่งที่ส่งไปมาระหว่าง client กับ server ไม่ใช่ตัว resource แต่เป็น representation ของมัน คือชุดไบต์พร้อม metadata ที่อธิบายไบต์เหล่านั้น เช่น JSON หรือ HTML ของผู้ใช้คนนั้น

constraint ของ REST ในบทที่ 5 มีดังนี้ (Fielding หัวข้อ 5.1):

Constraint ความหมาย
Client-server แยกหน้าที่ของ client กับ server ออกจากกัน
Stateless ทุก request ต้องมีข้อมูลครบสำหรับให้ server เข้าใจ session state เก็บไว้ที่ client ทั้งหมด
Cache response ต้องบอกได้ว่า cache ได้หรือไม่ได้
Uniform interface ทุก resource ใช้ interface เดียวกัน Fielding เรียกข้อนี้ว่าลักษณะสำคัญที่สุดที่ทำให้ REST ต่างจากสไตล์อื่น
Layered system แต่ละ component เห็นแค่ layer ที่ติดกับตัวเอง จึงวาง proxy, cache หรือ gateway คั่นกลางได้
Code-on-demand server ส่ง code ให้ client รันได้ เป็นข้อเดียวที่ไม่บังคับ

uniform interface ยังแบ่งย่อยเป็นสี่ข้อ คือ ระบุ resource ได้ จัดการ resource ผ่าน representation ข้อความอธิบายตัวเองได้ (self-descriptive) และ hypermedia as the engine of application state หรือที่มักเรียกย่อว่า HATEOAS

ฝั่ง HTTP ก็สร้างตามแนวนี้ RFC 9110 (มิถุนายน 2022) เขียนว่า HTTP ให้ uniform interface สำหรับทำงานกับ resource ไม่ว่า resource นั้นจะเป็นอะไรหรือ implement อย่างไร และ method มาตรฐานของ HTTP ไม่ผูกกับ resource ใด resource หนึ่ง (หัวข้อ 9.1)

RPC กับ REST ต่างกันตรงไหน

ลองดู request ที่ทำงานเดียวกัน คือขอข้อมูลผู้ใช้หมายเลข 42 แบบ JSON-RPC 2.0 จะส่งชื่อ method และพารามิเตอร์ไปใน body (JSON-RPC 2.0 Specification):

{"jsonrpc": "2.0", "method": "getUser", "params": {"id": 42}, "id": 1}

แบบ REST บน HTTP จะระบุ resource ไว้ใน URL และให้ method บอกการกระทำ:

GET /users/42 HTTP/1.1
Host: api.example.com
Accept: application/json

จากตัวอย่างนี้ ความต่างแยกได้เป็นห้าเรื่องครับ

request ระบุอะไร

RPC ระบุ การกระทำ ใน JSON-RPC คือ member method ซึ่งเป็นชื่อของ method ที่จะเรียก ใน gRPC คือ path แบบ /ชื่อ Service/ชื่อ method เช่น /UserService/GetUser

REST ระบุ ข้อมูล คือ URL ของ resource เช่น /users/42 แล้วใช้ method ตัวเดิมกับทุก resource Fielding สรุปความต่างข้อนี้ไว้ว่าใน HTTP นั้น request ถูกส่งไปที่ resource ผ่าน interface กลางที่มีความหมายมาตรฐาน ซึ่งตัวกลางเข้าใจได้ ส่วน RPC ถูกนิยามด้วย API ของภาษาโปรแกรม

HTTP method มีความหมายหรือไม่

ใน REST method มีความหมายที่ทุกคนตกลงกันไว้ใน RFC 9110 GET, HEAD, OPTIONS และ TRACE เป็น safe method คือไม่ควรเปลี่ยนแปลงอะไรบน server ส่วน PUT, DELETE และ safe method ทั้งหมด เป็น idempotent คือส่งซ้ำกี่ครั้งผลก็เหมือนส่งครั้งเดียว POST ไม่อยู่ในทั้งสองกลุ่ม

ใน RPC ที่วิ่งบน HTTP method มักไม่ได้บอกอะไร gRPC ส่งทุก call เป็น POST พร้อม content-type application/grpc ไม่ว่า method นั้นจะอ่านข้อมูลหรือลบข้อมูล ความหมายจริงอยู่ในชื่อ method ซึ่งมีแค่ client กับ server ที่รู้

error ส่งกลับมาอย่างไร

REST บน HTTP ใช้ status code ของ HTTP ตัวเลขหลักแรกบอกประเภทของ response เช่น 2xx สำเร็จ 4xx client ผิด 5xx server ผิด ถ้าไม่มีผู้ใช้หมายเลข 42 ก็ตอบ 404 Not Found

RPC กำหนด error ของตัวเอง JSON-RPC ส่ง error object ที่มี code, message และ data เช่น -32601 แปลว่า Method not found:

{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": 1}

gRPC มี status code ของตัวเองตั้งแต่ 0 (OK) ถึง 16 (UNAUTHENTICATED) และส่งไว้ใน trailer ชื่อ grpc-status ตัว HTTP status ของ gRPC error response เป็น 200 (OK) ดังนั้นถ้าดูแค่ HTTP status จะไม่รู้ว่า call นั้นสำเร็จหรือล้มเหลวครับ

retry ได้ปลอดภัยหรือไม่

ข้อนี้สำคัญเวลาเครือข่ายมีปัญหา RFC 9110 บอกว่าเพราะ method บางตัวเป็น idempotent เมื่อการเชื่อมต่อล้มเหลวก่อน client จะอ่าน response ได้ request นั้นส่งซ้ำอัตโนมัติได้ client, proxy หรือ library จึงตัดสินใจ retry GET หรือ PUT ได้เองโดยไม่ต้องรู้ว่า API ทำอะไร

RPC ไม่มีข้อตกลงกลางแบบนี้ RFC 5531 บอกว่าถ้า client ส่ง call ซ้ำแล้วได้ reply กลับมาหนึ่งครั้ง client สรุปไม่ได้ว่า procedure ถูกรันไปกี่ครั้ง รู้แค่ว่ารันอย่างน้อยหนึ่งครั้ง gRPC ก็เตือนไว้ที่ status UNAVAILABLE ว่าการ retry operation ที่ไม่ idempotent ไม่ปลอดภัยเสมอไป คนออกแบบ API ต้องบอกเองว่า method ไหน retry ได้

cache และตัวกลางทำอะไรได้บ้าง

REST บังคับให้ response ระบุว่า cache ได้หรือไม่ได้ และเพราะ interface เหมือนกันทุก resource ตัวกลางอย่าง cache, proxy และ gateway จึงเข้าใจ request ได้โดยไม่ต้องรู้จัก API นั้น

ข้อดีนี้มีราคา Fielding เขียนไว้เองว่า uniform interface ลดประสิทธิภาพลง เพราะข้อมูลถูกส่งในรูปแบบมาตรฐาน ไม่ใช่รูปแบบที่ตรงกับความต้องการของแอปพลิเคชัน และ stateless ก็อาจทำให้ประสิทธิภาพเครือข่ายลดลง เพราะต้องส่งข้อมูลซ้ำในทุก request RPC เลือกอีกทาง คือกำหนด method และรูปแบบข้อมูลให้ตรงกับงานแต่ละงาน แลกกับการที่ตัวกลางเข้าใจ request ได้น้อยลง

สรุปเป็นตารางได้ดังนี้

ประเด็น RPC REST
request ระบุอะไร ชื่อ procedure และพารามิเตอร์ URL ของ resource
HTTP method มักเป็น POST ทั้งหมด หรือไม่ได้ใช้ HTTP เลย มีความหมายตาม RFC 9110
error รูปแบบของ protocol นั้น เช่น JSON-RPC error object หรือ grpc-status HTTP status code
retry อัตโนมัติ ต้องรู้ว่า method นั้น idempotent หรือไม่ ตัดสินจาก method ได้
ตัวกลาง (cache, proxy) เข้าใจได้น้อย เข้าใจได้ เพราะ interface เหมือนกันทุก resource
สัญญาระหว่าง client กับ server กำหนดใน IDL หรือ spec ของ method URL, method และ media type

API ที่ใช้ HTTP เป็น REST ทุกตัวหรือไม่

ไม่ใช่ครับ Fielding เขียนไว้ในวิทยานิพนธ์ว่าคนมักเข้าใจผิดว่า HTTP เป็นกลไก RPC เพียงเพราะมี request และ response และในบล็อกปี 2008 เขายกตัวอย่าง API ที่ตั้งชื่อว่า "REST API" แล้วบอกตรง ๆ ว่า "That is RPC."

เกณฑ์ของ Fielding คือถ้าสถานะของแอปพลิเคชันไม่ได้ขับเคลื่อนด้วย hypertext ก็ไม่ใช่ RESTful client ควรเริ่มได้โดยรู้แค่ URL แรกและ media type มาตรฐาน แล้วไปต่อตาม link ที่ server ส่งกลับมา

ตามเกณฑ์นี้ API ส่วนใหญ่ที่เรียกตัวเองว่า REST ใช้แค่บางส่วนของ REST เช่น ใช้ URL เป็น resource และใช้ HTTP method ให้ถูกความหมาย แต่ไม่ได้ใช้ hypermedia ข้อนี้ไม่ได้แปลว่า API แบบนั้นใช้ไม่ได้ แค่ควรรู้ว่ากำลังได้ข้อดีข้อไหนของ REST และไม่ได้ข้อไหนครับ

gRPC และ JSON-RPC ทำงานอย่างไร

JSON-RPC 2.0 เป็น RPC protocol ที่ไม่เก็บ state เบาและไม่ผูกกับ transport ใช้บน HTTP, WebSocket หรือช่องทางอื่นก็ได้ request มี member สี่ตัว คือ jsonrpc (ต้องเป็น "2.0"), method, params (ไม่บังคับ) และ id ถ้าไม่ใส่ id จะกลายเป็น notification ซึ่ง server ต้องไม่ตอบกลับ และส่ง request หลายตัวพร้อมกันเป็น array ได้ โดย response อาจกลับมาไม่ตรงลำดับ จึงต้องจับคู่ด้วย id

gRPC เป็น RPC framework ที่ใช้ transport บน HTTP/2 และใช้ Protocol Buffers เป็น IDL (Interface Definition Language) โดยค่าเริ่มต้น เราเขียน service และ message ไว้ในไฟล์ .proto แล้ว generate code ของ client และ server จากไฟล์นั้น

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
}

gRPC มี method สี่แบบ คือ unary (ส่งหนึ่ง รับหนึ่ง) server streaming, client streaming และ bidirectional streaming client กำหนด deadline ได้ว่าจะรอนานเท่าไร ถ้าเกินจะจบด้วย DEADLINE_EXCEEDED และถ้ายกเลิก call กลางทาง สิ่งที่เปลี่ยนไปแล้วจะไม่ถูก rollback

ข้อจำกัดที่เจอบ่อยคือ browser ใช้ gRPC แบบปกติไม่ได้ เพราะ browser ไม่เปิดให้เข้าถึง HTTP/2 framing จึงต้องใช้ gRPC-Web ซึ่งเป็น protocol ที่ต่างจาก gRPC ปกติ และใน tutorial ของ gRPC เองใช้ Envoy proxy แปลง request จาก browser ไปที่ server

ใช้ทั้งสองแบบใน API เดียวกันได้หรือไม่

ได้ครับ ตัวอย่างที่ชัดคือแนวทางการออกแบบ API ของ Google (AIP) ซึ่งเรียกการออกแบบแบบ resource-oriented ว่าเป็นรูปแบบสำหรับกำหนด RPC API ที่ยืมหลักการหลายข้อมาจาก REST

API แบบนี้มี standard method ห้าตัว คือ Get, List, Create, Update และ Delete ซึ่งตรงกับการทำงานกับ resource และ AIP บอกว่าควรใช้ standard method ก่อน custom method ถ้างานไหนไม่ตรงกับห้าตัวนี้ ค่อยใช้ custom method ซึ่งให้อิสระในการออกแบบเท่ากับ RPC API แบบดั้งเดิม โดยใส่ : ตามด้วยชื่อการกระทำใน URL (AIP-136) เช่น

POST /v1/publishers/123/books/456:archive

ลำดับที่ AIP แนะนำคือ standard method ก่อน แล้วค่อยเป็น batch หรือ aggregate method ต่อด้วย custom method และสุดท้ายคือ streaming method (AIP-130)

ฝั่ง network security ควรรู้อะไรเรื่องนี้

ประเด็นแรกคือสิ่งที่ firewall หรือ proxy เห็น ถ้าเป็น REST ตัวกลางรู้ว่า request นี้อ่านหรือเขียนข้อมูลจาก method และรู้ผลลัพธ์จาก status code ถ้าเป็น gRPC ทุก call เป็น POST ที่ path ต่างกัน และ HTTP status เป็น 200 แม้ call จะล้มเหลว (PROTOCOL-HTTP2) log ที่เก็บแค่ method และ status code จึงแยก call ที่สำเร็จออกจาก call ที่ล้มเหลวไม่ได้ ต้องดู path และ grpc-status ด้วย

ประเด็นที่สองคือ layer Fielding เขียนถึง layered system ว่าทำให้บังคับใช้ security policy กับข้อมูลที่ข้ามขอบเขตขององค์กรได้ อย่างที่ firewall ต้องทำ และเขียนอีกที่ว่าการใช้ HTTP เป็นทางส่ง protocol อื่นผ่าน firewall เป็นความคิดที่ผิดตั้งแต่ต้น (หัวข้อ 6.5.3) ถ้า request ทุกตัวหน้าตาเป็น POST เหมือนกันหมด policy ที่อิงแค่ method ก็ใช้แยกอะไรไม่ได้

ควรเลือกใช้แบบไหน

ไม่มีแบบไหนดีกว่าในทุกกรณีครับ ให้ดูว่างานต้องการอะไร

  • API ที่ client หลายเจ้าเรียก โดยเฉพาะจาก browser: แนวทาง REST บน HTTP ตรงกว่า เพราะใช้ method, status code และ cache ที่ทุกเครื่องมือเข้าใจ ส่วน gRPC ต้องใช้ gRPC-Web และ proxy เพิ่ม
  • service ภายในที่คุยกันเยอะและต้องการ contract ชัด: gRPC ให้ไฟล์ .proto เป็นสัญญากลาง generate code ได้ และมี deadline กับ streaming มาให้
  • ต้องส่งข้อมูลต่อเนื่องสองทาง: gRPC มี bidirectional streaming ใน protocol อยู่แล้ว
  • งานที่ไม่ใช่การอ่านหรือแก้ข้อมูล เช่น "สั่ง archive" หรือ "เริ่มงาน": เขียนเป็น custom method แบบ AIP-136 ได้ ไม่ต้องบิดให้เป็น resource
  • ต้องการ RPC แบบเบา ๆ ไม่ผูก transport: JSON-RPC 2.0 มีแค่ JSON และ spec สั้น ๆ

ไม่ว่าเลือกแบบไหน มีสองเรื่องที่ควรเขียนไว้ใน API ให้ชัด คือ method ไหน retry ได้ และ error แต่ละแบบหน้าตาเป็นอย่างไร เพราะเป็นสองเรื่องที่ RPC กับ REST ต่างกันมากที่สุดครับ

แหล่งอ้างอิง

อ่านต่อ