CodeGym /Các khóa học /Go SELF /JSON API: tieu de, DTO va error envelope dong nhat

JSON API: tieu de, DTO va error envelope dong nhat

Go SELF
Mức độ, Bài học
Có sẵn

1. Tai sao can co JSON contract co dinh

Khi moi nguoi nghe "JSON API", ho thuong nghi: "Thi chi don gian thoi: tra JSON, nhan JSON". Nhung "chi JSON" cung giong nhu "chi thoa thuan gap nhau": neu khong co gio, dia diem va cau "ban dang mong toi o dinh dang nao", cuoc gap se bien thanh mot bo phim. JSON API contract khong chi la "nhung truong nao", ma con la "nhung header nao", "lam gi khi co loi", "client nhan dien van de the nao ma khong can phan tich tung chu trong text".

Neu ban khong co dinh contract, ban se gap loi co dien: frontend doi truong done, backend doi ten thanh isDone, khong co test, va nguoi dung "tu nhien" luc nao cung thay "chua hoan thanh". Va do con la kich ban tot nhat.

De contract ro rang va on dinh, hom nay chung ta se cung co dinh ba dieu:

  1. khi nao va nhu the nao nen dung Content-Type va Accept,
  2. cach thiet ke JSON cho request va response (thong qua DTO),
  3. cach tra loi mot cach nhat quan thong qua error envelope: { "error": { "code": "...", "message": "...", "fields": { ... } } }.

2. Header cua JSON API: Content-Type va Accept

Nhieu nguoi coi cac header trong HTTP la "thu yeu", vi "cai quan trong nhat van la body". Nhung trong thuc te, header giong nhu nhan dan tren thung hang khi giao hang. Tat nhien ban co the giao mot goi do khong ghi nhan, nhung sau do nguoi ta se phai lac, ngu hoi va doan: "Day co chac la cai coc, khong phai cuc gach khong?". Header giup khong phai doan mo.

Content-Type: no la gi va de lam gi

Content-Type la header noi ve thu ban gui trong body. Neu ban gui JSON trong request (vi du, tao mot task), request cua ban phai noi ro: "ben trong la JSON".

Vi du dien hinh:

POST /api/v1/tasks HTTP/1.1
Content-Type: application/json

{"title":"buy milk"}

Neu khong co Content-Type hoac no ghi mot gi do khac, server hoan toan co quyen noi: "Toi khong biet doc cai nay nhu the nao" — va tra ve loi. Thuong thi trong HTTP nguoi ta dung status 415 Unsupported Media Type, nhung hien tai dieu quan trong khong phai la so hoc, ma la y tuong contract.

Mot diem quan trong: neu request khong co body (vi du, nhieu GET), thi Content-Type thuong khong can thiet. Dung gui Content-Type: application/json "cho chac an": day giong nhu ghi len mot phong bi trong khong "ben trong la banh".

Accept: no la gi va de lam gi

Accept la header noi ve thu client muon nhan. Trong mot JSON API don gian, client thuong noi: "Toi mong doi application/json".

Vi du:

GET /api/v1/tasks HTTP/1.1
Accept: application/json

Neu client khong chi ro Accept, server van co the tra ve JSON (dac biet neu API chi cho JSON), nhung mot contract tot thi thich su ro rang.

Mot diem tam ly quan trong: Content-Type la "day la thu toi mang den cho ban", con Accept la "day la thu toi muon nhan tu ban". Do la hai vai tro khac nhau, giong nhu "giao giay to" va "nhan bien lai".

Bang nho nho de ghi nho

De bo nao khong phai giu dieu nay duoi dang "ma thuat cua header", ta co the nhin bang sau cho gon:

Header Ai dat No noi ve dieu gi Khi nao can
Content-Type
nguoi gui body dinh dang cua body (vi du JSON) khi co body
Accept
client dinh dang cua response duoc mong doi gan nhu luc nao cung can, neu client quan tam den dinh dang

3. Dinh dang request/response: tai sao can DTO

Khi thiet ke API, ban rat muon lay luon cau truc domain Task va chi them tag json:"...", de "tat ca tu dong chay". Dieu do co ve tiet kiem thoi gian, nhung thuong lai la mot khoan no voi lai suat 300%/nam: vui nho ngay bay gio va sua lon ve sau.

Ly do don gian: domain model chiu trach nhiem ve y nghia va quy tac cua linh vuc nghiep vu, con JSON contract chiu trach nhiem ve dinh dang giao tiep ben ngoai voi client. Do la hai trach nhiem khac nhau. Neu tron chung vao mot, ban se bat dau so thay doi domain vi "lam hong API", va so thay doi API vi "lam hong domain". Thanh ra mot sandwich kien truc lam tu so hai.

Vay nen chung ta dua vao DTO (Data Transfer Object): cac cau truc JSON "o ben ngoai". Con cac kieu domain song "o ben trong" va khong can phai biet JSON la gi.

DTO cho task

Hay tuong tuong ung dung hoc tap cua chung ta la mot "task tracker" (danh sach cong viec). O muc API, chung ta muon:

  • tao task bang tieu de,
  • lay mot task,
  • lay danh sach task.

Cac truong toi thieu de hieu trong JSON:

  • id — so,
  • title — chuoi,
  • done — gia tri logic.

Ta tao DTO:

package main

type TaskDTO struct {
	ID    int    `json:"id"`
	Title string `json:"title"`
	Done  bool   `json:"done"`
}

Con voi request tao task (thong thuong client khong truyen id — server se cap no):

package main

type CreateTaskRequest struct {
	Title string `json:"title"`
}

Hay chu y: chung ta co chu y khong dung title,omitempty — vi "title trong" cua mot task thuong la loi, va chung ta muon viec thieu/ trong ro rang lo ra trong qua trinh validation, chu khong phai "bien mat".

JSON se trong nhu the nao ma khong can server

Hien tai chung ta chua khoi dong HTTP server (do se la mot chu de khac), nhung format JSON thi chung ta co the debug ngay bay gio "trong chan khong".

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	req := CreateTaskRequest{Title: "buy milk"}

	b, _ := json.MarshalIndent(req, "", "  ")
	fmt.Println(string(b))
	// {
	//   "title": "buy milk"
	// }
}

Day la mot ky thuat don gian nhung cuc ky huu ich: ban nhin thay ro minh dang cam ket gi voi client.

4. Error envelope dong nhat: contract cho loi

Loi la noi API thuong tro thanh mot "vuon thu". Mot endpoint tra ve {"error":"bad"}, endpoint khac tra {"message":"oops"}, endpoint thu ba thi tra thang van ban "panic: runtime error". Phia client bat dau viet: "neu co field error thi la loi, neu co field message thi cung la loi, neu tra ve HTML thi... chac cung la loi".

De tranh dieu do, chung ta co dinh mot dinh dang loi thong nhat cho toan bo API. Do chinh la error envelope.

Y tuong nay rat hop voi cach tiep can cua Go: "errors as values" — loi la gia tri, co the tao ra, boc lai, phan loai va hien thi mot cach on dinh.

Dinh dang kinh dien

Contract cua module nay:

{
  "error": {
    "code": "...",
    "message": "...",
    "fields": { "...": "..." }
  }
}

Quy tac:

  • error.code — ma may (cho logic cua client),
  • error.message — van ban ngan, an toan (cho con nguoi),
  • error.fields — chi xuat hien duy nhat voi validation errors, nhu "truong → van de".

Diem quan trong: fields khong duoc tro thanh thung rac "tat ca vao day". Client se ky vong fields co nghia la "loi theo tung truong dau vao cu the".

Cau truc envelope trong Go

Dinh nghia envelope sao cho JSON luon khop voi contract.

package main

type APIError struct {
	Code    string            `json:"code"`
	Message string            `json:"message"`
	Fields  map[string]string `json:"fields,omitempty"`
}

type ErrorEnvelope struct {
	Error APIError `json:"error"`
}

Tai sao Fields co omitempty? Vi trong phan lon cac loi, fields khong can thiet, va chung ta khong muon luc nao cung gui "fields": {}. Su trong rong phai la su trong rong.

Tai sao can ca code lan message

Doi khi muon chi de lai message, vi "con nguoi co doc". Nhung may tinh cung doc — ung dung client, script, cac tich hop. Neu client muon phan biet not_found voi validation, no khong nen phai phan tich chuoi va tim "not found" nhu trong mot tro choi nhiem vu.

Va doi khi muon chi de lai code va bo message, vi "de frontend tu viet text". Dieu do cung khong tot: ban se mat UX thong nhat, va cac client khac nhau bat dau hien thi cac thong diep khac nhau. Voi mot API hoc tap nho, dieu huu ich hon la server tra ve ca ma may lan thong diep ngan.

O day co the nhac lai y tuong trong cac thuc hanh Go: khi ban lam mot thu co the nhan dien duoc va on dinh, ban bien no thanh mot phan cua API contract, va sua doi no sau nay se dau. Vi vay error envelope duoc co dinh nhu mot "contract".

5. Vi du ve loi: validation, not_found, internal

Loi nen duoc "nhin thay bang mat", khong chi mo ta bang loi noi. Ta se lam lai dieu do thong qua json.MarshalIndent, de co the kiem tra hinh dang JSON ma khong can khoi dong server.

Validation error voi fields

Kich ban dien hinh: client gui title rong.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	env := ErrorEnvelope{
		Error: APIError{
			Code:    "validation",
			Message: "invalid request",
			Fields:  map[string]string{"title": "must not be empty"},
		},
	}

	b, _ := json.MarshalIndent(env, "", "  ")
	fmt.Println(string(b))
	// {
	//   "error": {
	//     "code": "validation",
	//     "message": "invalid request",
	//     "fields": {
	//       "title": "must not be empty"
	//     }
	//   }
	// }
}

Hay chu y hai diem.

Thu nhat, fields la mot dictionary, khong phai mot mang chuoi. Dieu nay tien cho client: co the to mau chinh xac truong form.

Thu hai, message o day la chung ("invalid request"), con chi tiet nam trong fields. Dieu nay giup message khong tro thanh mot cuon tieu thuyet 12 tap.

Not found: khong co fields

Khi khong tim thay tai nguyen (vi du, khong co task voi id nhu vay), fields thuong khong can thiet: do khong phai la "loi cua mot truong dau vao cu the", ma la su vang mat cua mot doi tuong.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	env := ErrorEnvelope{
		Error: APIError{
			Code:    "not_found",
			Message: "resource not found",
		},
	}

	b, _ := json.MarshalIndent(env, "", "  ")
	fmt.Println(string(b))
	// {
	//   "error": {
	//     "code": "not_found",
	//     "message": "resource not found"
	//   }
	// }
}

Chinh cho nay omitempty phat huy tac dung: fields khong xuat hien trong JSON, va client khong nghi: "A, co fields, vay chac day la validation".

Internal: thong diep an toan

Loi pho bien nhat cua nguoi moi trong API la "chi can tra luon err.Error() ra ngoai". Dieu nay doi khi con co ve tien loi ("vi o do dung la dieu da xay ra"), nhung trong the gioi thuc te ban se:

  • lam lo chi tiet ben trong (duong dan file, SQL, cau hinh),
  • vo tinh lam contract phu thuoc vao van ban loi,
  • va doi khi con lam lo ca bi mat.

Chung ta tao mot thong diep on dinh, vi du "internal error". Chi tiet se nam trong log, khong nam trong contract. Va do chinh la ran gioi can bang: cai gi la phan cua public API, va cai gi la implementation ben trong.

6. Cach lien ket error envelope voi loi Go

Bay gio den mot diem quan trong: error envelope la JSON format tren ranh gioi cua he thong, con ben trong ban van co cac loi Go binh thuong (error), duoc tra ve tu ham, boc lai va kiem tra bang errors.Is/errors.As (ban da hoc nhung dieu nay tu cac ngay truoc).

Y tuong rat don gian: ben trong ung dung, ban giu mot loi giau thong tin (co context), con o dau ra (trong HTTP) ban chuyen no thanh mot trong cac loai public:

  • validation → 400 + envelope co fields,
  • not_found → 404 + envelope khong co fields,
  • internal → 500 + envelope khong co chi tiet ben trong.

Tai sao nhu vay? Vi "error as value" cho phep giu context phong phu cho chuong trinh, nhung public contract phai on dinh va an toan.

Tu dien nho cua thong diep theo ma

De khong lam cac thong diep "tien den dau hay den do" o nhieu noi khac nhau, rat tien de giu mot tu dien nho. Day khong phai la "kien truc hoan hao", ma chi la ky luat.

package main

func messageFor(code string) string {
	switch code {
	case "validation":
		return "invalid request"
	case "not_found":
		return "resource not found"
	default:
		return "internal error"
	}
}

Dung, no giong nhu mot "mini i18n", chi la khong co localizations. Dieu quan trong nhat la su on dinh.

Constructor cua envelope

De khong phai viet moi lan ErrorEnvelope{Error: APIError{...}}, ta tao mot ham nho:

package main

func newErrorEnvelope(code string, fields map[string]string) ErrorEnvelope {
	return ErrorEnvelope{
		Error: APIError{
			Code:    code,
			Message: messageFor(code),
			Fields:  fields,
		},
	}
}

Y nghia cua no khong nam o "ve dep", ma o cho contract loi duoc tap trung hoa. Neu tat ca loi duoc tao o mot noi, xac suat "vo tinh thay doi format" se thap hon nhieu.

7. Response thanh cong va loi trong cung mot phong cach

Ngay ca khong co server, ban van co the cho thay mot response thanh cong dien hinh va mot response loi dien hinh. Dieu nay giup giu trong dau rang API co hai nhanh ngang hang: thanh cong va loi. Khong co API "khong bao gio loi", chi co API "loi bat ngo".

Thanh cong: mot task

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	task := TaskDTO{ID: 1, Title: "buy milk", Done: false}

	b, _ := json.MarshalIndent(task, "", "  ")
	fmt.Println(string(b))
	// {
	//   "id": 1,
	//   "title": "buy milk",
	//   "done": false
	// }
}

Loi: request khong hop le

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	env := newErrorEnvelope("validation", map[string]string{
		"title": "must not be empty",
	})

	b, _ := json.MarshalIndent(env, "", "  ")
	fmt.Println(string(b))
	// {
	//   "error": {
	//     "code": "validation",
	//     "message": "invalid request",
	//     "fields": {
	//       "title": "must not be empty"
	//     }
	//   }
	// }
}

Hinh dang loi khong phu thuoc vao tung endpoint. Do chinh la gia tri lon nhat: client co the viet mot bo xu ly loi va tai su dung no o moi noi.

JSON API contract nam o dau: so do

Thinh thoang hien mot hinh de bo nao dung tiep tuc "nhai" dieu nay thanh truu tuong.

flowchart LR
    C[Client] -->|HTTP request + Accept + JSON body| S[Server]
    S -->|2xx + JSON DTO| C
    S -->|4xx/5xx + JSON ErrorEnvelope| C

Y cua so do: client luon mong doi JSON (theo Accept), server luon tra ve hoac la du lieu DTO, hoac la error envelope. "Luon co mot hinh dang loi giong nhau" khong phai la cau be canh, ma la thu tiet kiem hang tuan cuoc doi khi tich hop.

8. Cac loi thuong gap

Loi so 1: nham lan Content-Type va Accept (hoac dat ca hai "cho chac").
Khi request co JSON body, Content-Type: application/json la bat buoc, neu khong server phai doan format. Accept la yeu cau cua client ve dinh dang response. Neu dat header mot cach may moc, ban de dang roi vao tinh huong client "doi JSON" nhung lai gui body nhu "khong hieu la gi", sau do ngoac nhien vi sao server khong doc duoc du lieu.

Loi so 2: tra ve cac dinh dang loi khac nhau o tung endpoint.
Thuong no bat dau vo hai: "o day tra ve chuoi, o kia tra ve object". Sau mot thang, client da co ba bo xu ly loi va mot cu soc than kinh. Error envelope dong nhat la ky luat giup API co the du doan duoc. Mot khi ban da co dinh no, hay doi xu voi no nhu mot public contract, khong phai nhu "ban thao co the cai tien sau".

Loi so 3: bien message thanh noi do chi tiet (hoac nguoc lai, chi de lai code).
Neu message chua chi tiet noi bo ("sql: no rows", duong dan file, doan stack trace), ban dang tiet lo qua nhieu hoac vo tinh lam van ban loi thanh mot phan cua contract. Neu chi de lai mot code, ban se co su khong dong nhat trong thong diep client va mat di UX thong nhat. Cap hop ly la code cho logic va message cho con nguoi.

Loi so 4: su dung fields sai muc dich.
fields nen co nghia la "loi validation theo cac truong dau vao cu the". Neu ban bat dau nhan dien vao do bat ky thu gi (vi du, "storage": "down"), client se khong con tin vao truong nay va se phai viet cac cach luon lach. Ket qua la ban mat di loi ich lon nhat: co the to mau cho nguoi dung thay ro cac van de dau vao cu the.

Loi so 5: tiet lo loi noi bo ra ngoai bang err.Error().
Loi noi bo nen duoc boc lai, ghi log va phan tich ben trong server — trong Go, dieu nay rat tu nhien vi errors la values va co the duoc cau truc hoa. Nhung ra ngoai, trong HTTP contract, ban phai tra ve mot thong diep on dinh va an toan. Neu khong, ban se "nung" implementation vao public API va tao ra nhung nghia vu ban khong he co y dinh.

1
Nhiệm vụ
Go SELF, mức độ, bài học
Đã khóa
Yêu cầu JSON
Yêu cầu JSON
1
Nhiệm vụ
Go SELF, mức độ, bài học
Đã khóa
Lỗi not_found
Lỗi not_found
1
Nhiệm vụ
Go SELF, mức độ, bài học
Đã khóa
Trình tạo lỗi
Trình tạo lỗi
1
Nhiệm vụ
Go SELF, mức độ, bài học
Đã khóa
Hợp đồng tạo
Hợp đồng tạo
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION