在 Go 中制作 REST 处理程序的有效方法(无需重复代码)?

目前我有太多处理程序的重复代码:


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 个处理程序,因此重复代码似乎不太容易维护。


//stackoverflow 不允许对细节过多的代码提出质疑,所以...这里的细节,那里的细节,甚至更多的细节...//


犯罪嫌疑人X
浏览 126回答 5
5回答

一只名叫tom的猫

您可以将通用逻辑移至单独的函数,并将每个处理程序中特定的所有内容传递给它。假设您具有以下类型和功能: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)    })}

撒科打诨

我建议看看go-kit。它主要设计用于使用六边形架构创建服务。它带来了很多实用功能来避免重复代码并专注于业务逻辑。它有很多可能不需要的功能,但由于它是一个工具包(而不是一个完整的框架),您可以自由地只使用您需要的部分。例子也很容易理解。

侃侃尔雅

我有这些实用功能 : 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)}

青春有我

通常 REST API 的/guest端点只有一个处理程序,它根据HTTP 方法决定要做什么: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()它应该做什么)

慕妹3146593

这里有很多方法可以避免重复,例如,您可以使用装饰器模式,您可以在其中定义如何解码/编码以及其他不包含您的业务逻辑的步骤。您可以查看两种有趣的方法:一种来自 Mat:https ://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html另一个是 go-kit 包(你可以在 github 上查看),但我建议你查看关于如何编写装饰器的想法而不是安装库,这可能对你的实现来说是一种矫枉过正。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go