REST API設(shè)計(jì)模式和反模式
RESTful API已經(jīng)成為構(gòu)建現(xiàn)代網(wǎng)絡(luò)應(yīng)用的事實(shí)標(biāo)準(zhǔn)。它們?cè)试S一個(gè)靈活和可擴(kuò)展的架構(gòu),可以很容易地被廣泛的客戶(hù)端所消費(fèi)。然而,設(shè)計(jì)一個(gè)既健壯又可維護(hù)的REST API是很有挑戰(zhàn)性的,特別是對(duì)于剛?cè)胄械拈_(kāi)發(fā)者。
在這篇文章中,我們將探討一些常見(jiàn)的REST API設(shè)計(jì)模式和開(kāi)發(fā)者應(yīng)該注意的反模式。我們還將提供Golang和Open API Schema的代碼片段來(lái)幫助說(shuō)明這些概念。
一、REST API設(shè)計(jì)模式
1.以資源為導(dǎo)向的架構(gòu)(ROA)
面向資源的架構(gòu)(ROA)是一種設(shè)計(jì)模式,強(qiáng)調(diào)資源在RESTful API中的重要性。資源是RESTful API的關(guān)鍵構(gòu)件,它們應(yīng)該被設(shè)計(jì)成易于消費(fèi)和操作的方式。
在Golang中實(shí)現(xiàn)ROA的一種方式是使用gorilla/mux包進(jìn)行路由。這里有一個(gè)例子:
r := mux.NewRouter()
r.HandleFunc("/users/{id}", getUser).Methods("GET")
r.HandleFunc("/users", createUser).Methods("POST")
r.HandleFunc("/users/{id}", updateUser).Methods("PUT")
r.HandleFunc("/users/{id}", deleteUser).Methods("DELETE")
在Open API Schema中,你可以使用path參數(shù)來(lái)定義資源。下面是一個(gè)例子:
paths:
/users/{id}:
get:
…
put:
…
delete:
…
/users:
post:
…
2. HATEOAS
超媒體作為應(yīng)用狀態(tài)的引擎(HATEOAS)是一種設(shè)計(jì)模式,允許客戶(hù)動(dòng)態(tài)地瀏覽RESTful API。API提供超媒體鏈接,客戶(hù)可以按照這些鏈接來(lái)發(fā)現(xiàn)資源并與之互動(dòng)。
為了在GoLang中實(shí)現(xiàn)HATEOAS,你可以使用go-jsonapi包。這里有一個(gè)例子:
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Links *Links `json:"links,omitempty"`
}
type Links struct {
Self *Link `json:"self,omitempty"`
}
type Link struct {
Href string `json:"href,omitempty"`
}
func getUser(w http.ResponseWriter, r *http.Request) {
userID := mux.Vars(r)["id"]
user := User{ID: userID, Name: "John Doe"}
user.Links = &Links{
Self: &Link{Href: fmt.Sprintf("/users/%s", userID)},
}
jsonapi.MarshalOnePayload(w, &user)
}
在Open API Schema中,你可以使用links參數(shù)來(lái)定義超媒體鏈接。這里有一個(gè)例子:
paths:
/users/{id}:
get:
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/User'
links:
self:
href: '/users/{id}'
二、REST API反模式
1.RPC式的API
遠(yuǎn)程過(guò)程調(diào)用(RPC)風(fēng)格的API是RESTful API設(shè)計(jì)中一個(gè)常見(jiàn)的反模式。RPC風(fēng)格的API暴露了直接映射到底層實(shí)現(xiàn)的方法,而不是專(zhuān)注于資源。
下面是一個(gè)GoLang中RPC風(fēng)格API的例子:
func getUser(w http.ResponseWriter, r *http.Request) {
userID := r.FormValue("id")
user := userService.GetUser(userID)
json.NewEncoder(w).Encode(user)
}
在Open API Schema中,你可以使用operationId參數(shù)來(lái)定義RPC風(fēng)格的API。下面是一個(gè)例子:
paths:
/users:
get:
operationId: getUser
2.過(guò)度的工程設(shè)計(jì)
過(guò)度工程是RESTful API設(shè)計(jì)中另一個(gè)常見(jiàn)的反模式。當(dāng)開(kāi)發(fā)者試圖預(yù)測(cè)每一個(gè)可能的用例并建立一個(gè)復(fù)雜的API來(lái)適應(yīng)它們時(shí),就會(huì)出現(xiàn)過(guò)度設(shè)計(jì)。
這里有一個(gè)Golang中過(guò)度工程的例子:
func getUser(w http.ResponseWriter, r *http.Request) {
userID := mux.Vars(r)["id"]
user, err := userService.GetUser(userID)
if err != nil {
handleError(w, err)
return
}
json.NewEncoder(w).Encode(user)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
handleError(w, err)
return
}
user.ID = uuid.New().String()
user.CreatedAt = time.Now()
user.UpdatedAt = time.Now()
err = userService.CreateUser(user)
if err != nil {
handleError(w, err)
return
}
json.NewEncoder(w).Encode(user)
}
func updateUser(w http.ResponseWriter, r *http.Request) {
userID := mux.Vars(r)["id"]
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
handleError(w, err)
return
}
user.ID = userID
user.UpdatedAt = time.Now()
err = userService.UpdateUser(user)
if err != nil {
handleError(w, err)
return
}
json.NewEncoder(w).Encode(user)
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
userID := mux.Vars(r)["id"]
err := userService.DeleteUser(userID)
if err != nil {
handleError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
func handleError(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, err. Error())
}
在Open API Schema中,你可以使用x-go-genie擴(kuò)展定義過(guò)度工程。這里有一個(gè)例子:
paths:
/users/{id}:
get:
x-go-genie:
serviceName: UserService
methodName: GetUser
put:
x-go-genie:
serviceName: UserService
methodName: UpdateUser
delete:
x-go-genie:
serviceName: UserService
methodName: DeleteUser
/users:
post:
x-go-genie:
serviceName: UserService
methodName: CreateUser
總結(jié)
設(shè)計(jì)一個(gè)既健壯又可維護(hù)的RESTful API可能具有挑戰(zhàn)性,但通過(guò)遵循最佳實(shí)踐并避免常見(jiàn)的反模式,開(kāi)發(fā)人員可以創(chuàng)建易于消費(fèi)和操作的API。在這篇文章中,我們探討了一些常見(jiàn)的REST API設(shè)計(jì)模式和反模式,并提供了GoLang和Open API Schema的代碼片段來(lái)幫助說(shuō)明這些概念。