在GO中制作休息处理程序的有效方法(无重复代码)?

发布于 2025-01-27 21:16:42 字数 2228 浏览 4 评论 0原文

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

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
    ...
}

编写处理程序createGuestHandlerupdateguesuesthandlergetGuestHandler而不是重复类似的类似方法代码块三次。我想我可以使用接口,但不确定如何编写。我有大约20个处理程序,因此重复代码似乎无法真正维护。

// Stackoverflow不允许详细信息过多的代码,因此...在此处的详细信息,详细信息,甚至更多详细信息... //

Currently I have too much repeated code for the handlers:

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
    ...
}

Is there any nicer way to write the handlers createGuestHandler, updateGuestHandler and getGuestHandler instead of repeating similar code blocks three times. I guess I can use interface but am not sure how to write that. I have about 20 handlers so the repeating code does not seem really maintainable.

//stackoverflow does not allow question with too much code over details so... details here, details there, even more details...//

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

蓝天白云 2025-02-03 21:16:42

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

让我们假设您具有这些类型和功能:

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()updateguesuest())。

这就是它的外观:

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
}

没有仿制药

,该解决方案不使用仿制药(并且也可以与旧版本一起使用)。

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)
    })
}

You can move the common logic to a separate function, and pass everything to it that is specific in each handler.

Let's assume you have these types and functions:

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
}

With generics allowed

If generics are allowed, you can factor all code out of handlers:

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)
}

As you can see, all handler implementations are just a single line! We can even get rid of the handler functions now, as we can create a handler from a logic function (like CreateGuest(), UpdateGuest()).

This is how it would look like:

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)
        }
    }
}

And using it:

func NewGuestMux() *GuestMux {
    var guestMux = &GuestMux{}
    guestMux.HandleFunc("/guest/createguest", createHandler(CreateGuest))
    guestMux.HandleFunc("/guest/updateguest", createHandler(UpdateGuest))

    return guestMux
}

Without generics

This solution does not use generics (and works with old Go versions too).

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)
    })
}
世俗缘 2025-02-03 21:16:42

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

您可以检查两种有趣的方法:
一个来自mat: https://pace.dev/blog/2018/05/09/how-i-i-write-http-services-services-after-eight-eights.html

另一个是Go-Kit软件包(您可以在GitHub上查看它,但我建议您检查有关如何组合装饰器而不是安装图书馆的想法,这可能是您启动的过度杀伤。

There are many ways to avoid repetition here, for example, you could use a decorator pattern, where you can define how to decode/encode and other steps that doesn't include your business logic.

You can check two interesting approaches:
One is from Mat: https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html

The other one is the go-kit package (you can check it out on github), but I recommend you to checkout the idea on how to compose decorators instead of installing the library, could be an overkill for your implematation.

深空失忆 2025-02-03 21:16:42

通常,REST API只有/guest带有单个处理程序的端点,该端点决定基于 http方法

  • post create
  • get get 检索
  • put以更新整个记录
  • <代码>修补程序要更新某些字段,

您可以在处理程序内部查看r.method,并根据此确定要运行的代码。

如果您的问题中显示的界面注定,则可以EG包装处理程序到具有预期界面的匿名函数,并使其接受一个额外的参数来决定该怎么做:(

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)
})
...

在其中创建和更新是某种标志,这些标志告诉<代码> GuestHandler()它应该做什么)

Typically REST APIs have just /guest endpoint with single handler that decides what to do based on HTTP method:

  • POST to create
  • GET to retrieve
  • PUT to update the entire record
  • PATCH to update certain fields

You can look at r.Method inside your handler and decide what code to run based on that.

If you are bound to interface shown in your question you can e.g. wrap handler to an anonymous function with expected interface and make it accept an additional argument to decide what to do like this:

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)
})
...

(where CREATE and UPDATE are some sort of flags that tell guestHandler() what it should do)

向日葵 2025-02-03 21:16:42

我建议您看看 go-kit
它主要设计用于使用六角形体系结构创建服务。它带来了许多实用功能,以避免重复代码并专注于业务逻辑。

它具有很多可能不需要的功能,但是由于它是工具包(而不是完整的框架),因此您只需使用所需的零件。

示例也很容易遵循。

I suggest to have a look to go-kit.
It's mainly designed to create services using Hexagonal architecture. It brings a lot of utility functions to avoid repeated code and focus on the business logic.

It has a lot of functionality that may not need but since it's a toolkit (and not a complete framework) you're free to use only the parts that you need.

Examples are also easy to follow.

久随 2025-02-03 21:16:42

我具有这些实用程序函数:decodejsonbodyrecondjson我用来简化响应,而不会添加过多的复杂性。我将其包装在响应 struct中,用于发送客户端错误详细信息。

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)

}

I have these utility functions : decodeJsonBody, respondJson that I use to simplify response, without adding too much complexity. I wrap it in the Response struct for sending client side error details.

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)

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文