在 Go 中制作 REST 处理程序的有效方法(无需重复代码)?
Efficient way to make REST handlers in Go (without repeating code)?
目前我有太多处理程序的重复代码:
type GuestMux struct {
http.ServeMux
}
func main() {
guestMux := NewGuestMux()
http.ListenAndServe(":3001", guestMux)
}
func NewGuestMux() *GuestMux {
var guestMux = &GuestMux{}
guestMux.HandleFunc("/guest/createguest", createGuestHandler)
guestMux.HandleFunc("/guest/updateguest", updateGuestHandler)
guestMux.HandleFunc("/guest/getguest", getGuestHandler)
return guestMux
}
func createGuestHandler(w http.ResponseWriter, r *http.Request) {
var createGuestReq CreateGuestRequest
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = json.Unmarshal(reqBody, &createGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
resp, err := CreateGuest(&createGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
var updateGuestReq UpdateGuestRequest
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = json.Unmarshal(reqBody, &updateGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
resp, err := UpdateGuest(&updateGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func getGuestHandler(w http.ResponseWriter, r *http.Request) {
// almost the same as above two handlers, just different method to call and
// its parameter type
...
}
有没有更好的方法来编写处理程序 createGuestHandler
、updateGuestHandler
和 getGuestHandler
而不是重复类似的代码块三次。我想我可以使用 interface
但我不确定如何写。我有大约 20 个处理程序,所以重复的代码似乎不太容易维护。
//Whosebug 不允许在细节上使用过多代码的问题,所以...这里有细节,那里有细节,甚至更多细节...//
您可以将通用逻辑移至单独的函数,并将每个处理程序中特定的所有内容传递给它。
假设您有这些类型和函数:
type CreateGuestRequest struct{}
type UpdateGuestRequest struct{}
type CreateGuestResponse struct{}
type UpdateGuestResponse struct{}
func CreateGuest(v *CreateGuestRequest) (resp *CreateGuestResponse, err error) {
return nil, nil
}
func UpdateGuest(v *UpdateGuestRequest) (resp *UpdateGuestResponse, err error) {
return nil, nil
}
允许使用泛型
如果允许使用泛型,您可以从处理程序中提取 所有 代码:
func handle[Req any, Resp any](w http.ResponseWriter, r *http.Request, logicFunc func(dst Req) (Resp, error)) {
var dst Req
if err := json.NewDecoder(r.Body).Decode(&dst); err != nil {
log.Printf("Decoding body failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
resp, err := logicFunc(dst)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Encoding response failed: %v", err)
}
}
func createGuestHandler(w http.ResponseWriter, r *http.Request) {
handle(w, r, CreateGuest)
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
handle(w, r, UpdateGuest)
}
如您所见,所有处理程序实现都只是一行!我们现在甚至可以摆脱处理函数,因为我们可以从逻辑函数创建处理函数(如 CreateGuest()
、UpdateGuest()
)。
这是它的样子:
func createHandler[Req any, Resp any](logicFunc func(dst Req) (Resp, error)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var dst Req
if err := json.NewDecoder(r.Body).Decode(&dst); err != nil {
log.Printf("Decoding body failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
resp, err := logicFunc(dst)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Encoding response failed: %v", err)
}
}
}
并使用它:
func NewGuestMux() *GuestMux {
var guestMux = &GuestMux{}
guestMux.HandleFunc("/guest/createguest", createHandler(CreateGuest))
guestMux.HandleFunc("/guest/updateguest", createHandler(UpdateGuest))
return guestMux
}
没有泛型
此解决方案不使用泛型(也适用于旧的 Go 版本)。
func handle(w http.ResponseWriter, r *http.Request, dst interface{}, logicFunc func() (interface{}, error)) {
if err := json.NewDecoder(r.Body).Decode(dst); err != nil {
log.Printf("Decoding body failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
resp, err := logicFunc()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Encoding response failed: %v", err)
}
}
func createGuestHandler(w http.ResponseWriter, r *http.Request) {
var createGuestReq CreateGuestRequest
handle(w, r, &createGuestReq, func() (interface{}, error) {
return CreateGuest(&createGuestReq)
})
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
var updateGuestReq UpdateGuestRequest
handle(w, r, &updateGuestReq, func() (interface{}, error) {
return UpdateGuest(&updateGuestReq)
})
}
这里有很多方法可以避免重复,例如,您可以使用装饰器模式,您可以在其中定义如何 decode/encode 以及其他不包含您的业务逻辑的步骤。
您可以检查两种有趣的方法:
一个来自 Mat:https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html
另一个是 go-kit 包(你可以在 github 上查看),但我建议你查看关于如何编写装饰器的想法而不是安装库,可以对您的实施来说太过分了。
通常 REST API 只有 /guest
端点和单个处理程序,根据 HTTP method:
决定要做什么
POST
创建
GET
检索
PUT
更新整条记录
PATCH
更新某些字段
您可以在您的处理程序中查看 r.Method
并根据此决定要 运行 的代码。
如果您绑定到问题中显示的界面,您可以例如将处理程序包装到具有预期接口的匿名函数,并使其接受一个额外的参数来决定要做什么:
guestMux.HandleFunc("/guest/createguest", func(w http.ResponseWriter, r *http.Request) {
guestHandler(r, w, CREATE)
})
guestMux.HandleFunc("/guest/updateguest", func(w http.ResponseWriter, r *http.Request) {
guestHandler(r, w, UPDATE)
})
...
(其中 CREATE 和 UPDATE 是某种标志,告诉 guestHandler()
它应该做什么)
我有这些实用函数:decodeJsonBody
、respondJson
,我用它们来简化响应,而不增加太多复杂性。我将其包装在 Response
结构中,用于发送客户端错误详细信息。
type Response struct {
Data interface{} `json:"data"`
Errors interface{} `json:"errors"`
}
func respondJson(w http.ResponseWriter, data interface{}, err error) {
w.Header().Set("Content-Type", "application/json")
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err = json.NewEncoder(w).Encode(Response{
Errors: err.Error(),
})
return
}
err = json.NewEncoder(w).Encode(Response{
Data: data,
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("http handler failed to convert response to json %s\n", err)
}
}
func decodeJsonBody(r *http.Request, v interface{}) error {
decoder := json.NewDecoder(r.Body)
return decoder.Decode(v)
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
var updateGuestReq UpdateGuestRequest
err := decodeJsonBody(r, &updeateGuestReq)
if err != nil {
respondJson(w, nil, err)
return
}
data, err := UpdateGuest(&updateGuestReq)
respondJson(w, data, err)
}
建议去go-kit看看。
它主要设计用于使用六边形架构创建服务。它带来了很多实用功能来避免重复代码并专注于业务逻辑。
它有很多可能不需要的功能,但由于它是一个工具包(而不是一个完整的框架),您可以自由地只使用您需要的部分。
示例也很容易理解。
目前我有太多处理程序的重复代码:
type GuestMux struct {
http.ServeMux
}
func main() {
guestMux := NewGuestMux()
http.ListenAndServe(":3001", guestMux)
}
func NewGuestMux() *GuestMux {
var guestMux = &GuestMux{}
guestMux.HandleFunc("/guest/createguest", createGuestHandler)
guestMux.HandleFunc("/guest/updateguest", updateGuestHandler)
guestMux.HandleFunc("/guest/getguest", getGuestHandler)
return guestMux
}
func createGuestHandler(w http.ResponseWriter, r *http.Request) {
var createGuestReq CreateGuestRequest
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = json.Unmarshal(reqBody, &createGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
resp, err := CreateGuest(&createGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
var updateGuestReq UpdateGuestRequest
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = json.Unmarshal(reqBody, &updateGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
resp, err := UpdateGuest(&updateGuestReq)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func getGuestHandler(w http.ResponseWriter, r *http.Request) {
// almost the same as above two handlers, just different method to call and
// its parameter type
...
}
有没有更好的方法来编写处理程序 createGuestHandler
、updateGuestHandler
和 getGuestHandler
而不是重复类似的代码块三次。我想我可以使用 interface
但我不确定如何写。我有大约 20 个处理程序,所以重复的代码似乎不太容易维护。
//Whosebug 不允许在细节上使用过多代码的问题,所以...这里有细节,那里有细节,甚至更多细节...//
您可以将通用逻辑移至单独的函数,并将每个处理程序中特定的所有内容传递给它。
假设您有这些类型和函数:
type CreateGuestRequest struct{}
type UpdateGuestRequest struct{}
type CreateGuestResponse struct{}
type UpdateGuestResponse struct{}
func CreateGuest(v *CreateGuestRequest) (resp *CreateGuestResponse, err error) {
return nil, nil
}
func UpdateGuest(v *UpdateGuestRequest) (resp *UpdateGuestResponse, err error) {
return nil, nil
}
允许使用泛型
如果允许使用泛型,您可以从处理程序中提取 所有 代码:
func handle[Req any, Resp any](w http.ResponseWriter, r *http.Request, logicFunc func(dst Req) (Resp, error)) {
var dst Req
if err := json.NewDecoder(r.Body).Decode(&dst); err != nil {
log.Printf("Decoding body failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
resp, err := logicFunc(dst)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Encoding response failed: %v", err)
}
}
func createGuestHandler(w http.ResponseWriter, r *http.Request) {
handle(w, r, CreateGuest)
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
handle(w, r, UpdateGuest)
}
如您所见,所有处理程序实现都只是一行!我们现在甚至可以摆脱处理函数,因为我们可以从逻辑函数创建处理函数(如 CreateGuest()
、UpdateGuest()
)。
这是它的样子:
func createHandler[Req any, Resp any](logicFunc func(dst Req) (Resp, error)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var dst Req
if err := json.NewDecoder(r.Body).Decode(&dst); err != nil {
log.Printf("Decoding body failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
resp, err := logicFunc(dst)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Encoding response failed: %v", err)
}
}
}
并使用它:
func NewGuestMux() *GuestMux {
var guestMux = &GuestMux{}
guestMux.HandleFunc("/guest/createguest", createHandler(CreateGuest))
guestMux.HandleFunc("/guest/updateguest", createHandler(UpdateGuest))
return guestMux
}
没有泛型
此解决方案不使用泛型(也适用于旧的 Go 版本)。
func handle(w http.ResponseWriter, r *http.Request, dst interface{}, logicFunc func() (interface{}, error)) {
if err := json.NewDecoder(r.Body).Decode(dst); err != nil {
log.Printf("Decoding body failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
resp, err := logicFunc()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Encoding response failed: %v", err)
}
}
func createGuestHandler(w http.ResponseWriter, r *http.Request) {
var createGuestReq CreateGuestRequest
handle(w, r, &createGuestReq, func() (interface{}, error) {
return CreateGuest(&createGuestReq)
})
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
var updateGuestReq UpdateGuestRequest
handle(w, r, &updateGuestReq, func() (interface{}, error) {
return UpdateGuest(&updateGuestReq)
})
}
这里有很多方法可以避免重复,例如,您可以使用装饰器模式,您可以在其中定义如何 decode/encode 以及其他不包含您的业务逻辑的步骤。
您可以检查两种有趣的方法: 一个来自 Mat:https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html
另一个是 go-kit 包(你可以在 github 上查看),但我建议你查看关于如何编写装饰器的想法而不是安装库,可以对您的实施来说太过分了。
通常 REST API 只有 /guest
端点和单个处理程序,根据 HTTP method:
POST
创建GET
检索PUT
更新整条记录PATCH
更新某些字段
您可以在您的处理程序中查看 r.Method
并根据此决定要 运行 的代码。
如果您绑定到问题中显示的界面,您可以例如将处理程序包装到具有预期接口的匿名函数,并使其接受一个额外的参数来决定要做什么:
guestMux.HandleFunc("/guest/createguest", func(w http.ResponseWriter, r *http.Request) {
guestHandler(r, w, CREATE)
})
guestMux.HandleFunc("/guest/updateguest", func(w http.ResponseWriter, r *http.Request) {
guestHandler(r, w, UPDATE)
})
...
(其中 CREATE 和 UPDATE 是某种标志,告诉 guestHandler()
它应该做什么)
我有这些实用函数:decodeJsonBody
、respondJson
,我用它们来简化响应,而不增加太多复杂性。我将其包装在 Response
结构中,用于发送客户端错误详细信息。
type Response struct {
Data interface{} `json:"data"`
Errors interface{} `json:"errors"`
}
func respondJson(w http.ResponseWriter, data interface{}, err error) {
w.Header().Set("Content-Type", "application/json")
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err = json.NewEncoder(w).Encode(Response{
Errors: err.Error(),
})
return
}
err = json.NewEncoder(w).Encode(Response{
Data: data,
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("http handler failed to convert response to json %s\n", err)
}
}
func decodeJsonBody(r *http.Request, v interface{}) error {
decoder := json.NewDecoder(r.Body)
return decoder.Decode(v)
}
func updateGuestHandler(w http.ResponseWriter, r *http.Request) {
var updateGuestReq UpdateGuestRequest
err := decodeJsonBody(r, &updeateGuestReq)
if err != nil {
respondJson(w, nil, err)
return
}
data, err := UpdateGuest(&updateGuestReq)
respondJson(w, data, err)
}
建议去go-kit看看。 它主要设计用于使用六边形架构创建服务。它带来了很多实用功能来避免重复代码并专注于业务逻辑。
它有很多可能不需要的功能,但由于它是一个工具包(而不是一个完整的框架),您可以自由地只使用您需要的部分。
示例也很容易理解。