为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
犯罪嫌疑人X 2022-12-13 11:11:15
目前我有太多处理程序的重复代码: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 不允许对细节过多的代码提出质疑,所以...这里的细节,那里的细节,甚至更多的细节...//
查看完整描述

5 回答

?
一只名叫tom的猫

TA贡献1906条经验 获得超3个赞

您可以将通用逻辑移至单独的函数,并将每个处理程序中特定的所有内容传递给它。


假设您具有以下类型和功能:


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)

    })

}


查看完整回答
反对 回复 2022-12-13
?
撒科打诨

TA贡献1934条经验 获得超2个赞

我建议看看go-kit。它主要设计用于使用六边形架构创建服务。它带来了很多实用功能来避免重复代码并专注于业务逻辑。

它有很多可能不需要的功能,但由于它是一个工具包(而不是一个完整的框架),您可以自由地只使用您需要的部分。

例子也很容易理解。


查看完整回答
反对 回复 2022-12-13
?
侃侃尔雅

TA贡献1801条经验 获得超16个赞

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


}


查看完整回答
反对 回复 2022-12-13
?
青春有我

TA贡献1784条经验 获得超8个赞

通常 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()它应该做什么)


查看完整回答
反对 回复 2022-12-13
?
慕妹3146593

TA贡献1820条经验 获得超9个赞

这里有很多方法可以避免重复,例如,您可以使用装饰器模式,您可以在其中定义如何解码/编码以及其他不包含您的业务逻辑的步骤。

您可以查看两种有趣的方法:一种来自 Mat:https ://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html

另一个是 go-kit 包(你可以在 github 上查看),但我建议你查看关于如何编写装饰器的想法而不是安装库,这可能对你的实现来说是一种矫枉过正。


查看完整回答
反对 回复 2022-12-13
  • 5 回答
  • 0 关注
  • 121 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信