Errors & Status Codes
error เป็นส่วนหนึ่งของ contract
หัวข้อที่มีชื่อว่า “error เป็นส่วนหนึ่งของ contract”gRPC ไม่ใช้ HTTP status code ทุก call จบด้วย gRPC status: code ที่เป็นตัวเลข, message ที่มนุษย์อ่านได้ (optional), และ details แบบมีโครงสร้าง (optional) call ที่สำเร็จ return OK; อย่างอื่นคือ error ที่ client รับมาเป็น status ที่มี type ไม่ใช่ response body ที่ต้อง parse
การเลือก code ที่ถูกต้องคือการตัดสินใจออกแบบ — client แตกเงื่อนไขจาก code, retry ตาม code และถูกปลุกกลางดึกด้วย code code ที่คลุมเครือทำให้ API น่าหงุดหงิด; code ที่แม่นยำทำให้ API อธิบายตัวเองได้
status code ที่คุณจะใช้จริง
หัวข้อที่มีชื่อว่า “status code ที่คุณจะใช้จริง”มี code ประมาณ 17 ตัว แต่ไม่กี่ตัวครอบคลุมเกือบทุกอย่าง:
| Code | ความหมาย | สาเหตุที่พบบ่อย |
|---|---|---|
OK | สำเร็จ | — |
INVALID_ARGUMENT | request ผิดรูปแบบ | client ส่ง field ผิด; แก้ input แล้วจะช่วยได้ |
NOT_FOUND | entity ไม่มีอยู่ | GetUser กับ id ที่ไม่มี |
ALREADY_EXISTS | entity มีอยู่แล้ว | CreateUser ด้วย email ที่ถูกใช้ไปแล้ว |
PERMISSION_DENIED | authenticate แล้วแต่ไม่ได้รับอนุญาต | caller ไม่มี role |
UNAUTHENTICATED | ไม่มี credential ที่ถูกต้อง | token หายหรือผิด |
FAILED_PRECONDITION | state ของระบบไม่พร้อมสำหรับ call นี้ | ลบ bucket ที่ยังไม่ว่าง |
RESOURCE_EXHAUSTED | ชน quota หรือ rate limit | request มากเกินไป |
DEADLINE_EXCEEDED | call รันเกิน deadline | server หรือ network ช้า |
UNAVAILABLE | server ล่มหรือติดต่อไม่ได้ | ชั่วคราว; มักปลอดภัยที่จะ retry |
INTERNAL | บั๊กฝั่ง server | failure ที่ไม่คาดคิด |
มี 2 คู่ที่คนสับสนบ่อย:
INVALID_ARGUMENTvsFAILED_PRECONDITION— ตัวแรกหมายถึง “input คุณผิด แก้แล้วจะช่วยได้”; ตัวหลังหมายถึง “input คุณโอเค แต่ระบบไม่ได้อยู่ใน state ที่ทำสำเร็จได้”UNAUTHENTICATEDvsPERMISSION_DENIED— ตัวแรกหมายถึง “ฉันไม่รู้ว่าคุณเป็นใคร”; ตัวหลังหมายถึง “ฉันรู้ว่าคุณเป็นใคร และคุณไม่ได้รับอนุญาต”
UNAVAILABLE และ DEADLINE_EXCEEDED สำคัญต่อการ retry: ทั้งคู่บอกว่าเป็น failure ชั่วคราว client (และ interceptor) จึงมัก retry ต่อ ส่วน INVALID_ARGUMENT ไม่ควร retry แบบเดิมซ้ำเด็ดขาด
การ return error
หัวข้อที่มีชื่อว่า “การ return error”คุณไม่ได้ return ข้อความ error ใน response — คุณจบ call ด้วย status ทุกภาษาเปิดเผยไอเดียเดียวกัน:
import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status")
func (s *server) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.User, error) { u, ok := s.store[req.Id] if !ok { return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id) } return u, nil}import grpc
def GetUser(self, request, context): user = self.store.get(request.id) if user is None: context.abort(grpc.StatusCode.NOT_FOUND, f"user {request.id} not found") return userimport { status } from '@grpc/grpc-js';
getUser(call, callback) { const user = this.store.get(call.request.id); if (!user) { callback({ code: status.NOT_FOUND, message: `user ${call.request.id} not found` }); return; } callback(null, user);}ฝั่ง client จะได้ status เดียวกันกลับมาเป็น error ที่มี type ซึ่งพก code และ message มาด้วย — caller จึงแตกเงื่อนไข NOT_FOUND กับ UNAVAILABLE ได้โดยไม่ต้อง match string
Rich error ด้วย details
หัวข้อที่มีชื่อว่า “Rich error ด้วย details”code กับ message มักจะพอ เมื่อไม่พอ — เมื่อ client ต้องการรายละเอียดที่ machine-readable เช่น field ไหน validate ไม่ผ่าน — gRPC มี model ที่รวยกว่า: google.rpc.Status พก list details ของ message ที่มี type ได้หลายตัว
flowchart LR
err["gRPC status"] --> code["code: INVALID_ARGUMENT"]
err --> msg["message: validation failed"]
err --> det["details[]"]
det --> bad["BadRequest {
field: email,
desc: not a valid address }"] detail type มาตรฐาน (BadRequest, ErrorInfo, QuotaFailure, RetryInfo และอื่น ๆ จาก google.rpc) ทำให้ server บอกได้แม่น ๆ ว่า “field email ไม่ถูกต้อง” หรือ “retry หลังจาก 3 วินาที” และทำให้ client react แบบ programmatic ได้ ใช้ code เปล่า ๆ สำหรับเคสทั่วไป; หยิบ rich details มาใช้เมื่อ client ต้องการ error data แบบมีโครงสร้างจริง ๆ