如何在Golang中减少重复的HTTP处理程序代码?

huangapple go评论79阅读模式
英文:

How to reduce repetitive http handler code in golang?

问题

我正在设计一个Go语言的API服务器。我有许多数据库表,每个表都有一个对应的struct。每个表都有一个路由和处理程序:

type Thing1 struct {
   ID int64
   Name string
   ...
}

func main() {
    ...
    router := mux.NewRouter()
    apiRouter := router.PathPrefix("/v1").Subrouter()
    apiRouter.HandleFunc("/thing1/{id}", Thing1ShowHandler).Methods("GET")
}

func Thing1ShowHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)

    id, err := strconv.ParseInt(vars["id"], 10, 64)
    if err != nil {
        errorHandler(w, err)
        return
    }
    thing1 := Thing1{ID: id}
    err = db.First(&thing1, id).Error
    if thing1.ID > 0 {
        jsonHeaders(w, http.StatusOK)
        if err := json.NewEncoder(w).Encode(thing1); err != nil {
            errorHandler(w, err)
        }
        return
    }
    notFoundHandler(w, r)
}

Thing2的代码几乎完全相同,Thing3也是如此,以此类推。我最终会有成百上千个这样的东西,因此会有很多重复的代码。感觉我做错了什么。如何更好地使代码更加DRY(Don't Repeat Yourself)?

英文:

I'm designing a API server in Go. I have many database tables, each with a matching struct. Each has a route and handler:

type Thing1 struct {
   ID int64
   Name string
   ...
}

func main() {
    ...
 	router := mux.NewRouter()
    apiRouter := router.PathPrefix("/v1").Subrouter()
    apiRouter.HandleFunc("/thing1/{id}", Thing1ShowHandler).Methods("GET")
}

func Thing1ShowHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)

    id, err := strconv.ParseInt(vars["id"], 10, 64)
    if err != nil {
        errorHandler(w, err)
        return
    }
    thing1 := Thing1{ID: id}
    err = db.First(&thing1, id).Error
    if thing1.ID > 0 {
        jsonHeaders(w, http.StatusOK)
        if err := json.NewEncoder(w).Encode(thing1); err != nil {
            errorHandler(w, err)
        }
        return
    }
    notFoundHandler(w, r)
}

The code for Thing2 is pretty much identical, as it is for Thing3 and so on. I will end up with hundreds of things, and therefore lots of duplicated code. It feels like I'm doing something horribly wrong. What's the best way to make this more DRY?

答案1

得分: 4

为什么不为每个Thing创建一个用于http.Handler的工厂函数呢?这样可以将showHandler逻辑写一次,并通过参数化实例化单个Thing

// ThingFactory返回一个配置了给定ID的Thing结构体。
type ThingFactory func(id int64) interface{}

// createShowHandler函数是一个用于创建处理程序的工厂函数,
// 它使用getThing工厂函数获取一个Thing实例,
// 并在生成视图时使用该实例。
func createShowHandler(getThing ThingFactory) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		id, err := strconv.ParseInt(vars["id"], 10, 64)

		if err != nil {
			errorHandler(w, err)
			return
		}

		thing := getThing(id)
		err = db.First(&thing, id).Error

		if err != nil {
			errorHandler(w, err)
		}

		if thing1.ID > 0 {
			jsonHeaders(w, http.StatusOK)
			if err := json.NewEncoder(w).Encode(thing1); err != nil {
				errorHandler(w, err)
			}
			return
		}

		notFoundHandler(w, r)
	}
}

这个函数可以用来系统地为给定的路由器创建路由。例如,我可以创建一个显式的注册表,用于跟踪每个Thing的路径,以及一个在调用createShowHandler工厂函数时使用的ThingFactory实例。

router := mux.NewRouter()
apiRouter := router.PathPrefix("/v1").Subrouter()

registry := []struct {
	path    string
	handler ThingFactory
}{
	{"/thing1/{id}", func(id int64) interface{} { return Thing1{ID: id} }},
	{"/thing2/{id}", func(id int64) interface{} { return Thing2{ID: id} }},
	{"/thing3/{id}", func(id int64) interface{} { return Thing3{ID: id} }},
}

for _, registrant := range registry {
	apiRouter.HandleFunc(registrant.path, createShowHandler(registrant.handler)).Methods("GET")
}

当然,你可能希望为这样的程序定义各种交互点的接口,以在处理大量实例时获得更多的类型安全性。可以实现一个更健壮的注册表,为Thing提供一个接口,让它们自己注册。

英文:

Why not create a factory function for the http.Handler used with each Thing? This allows you to write the showHandler logic once and parameterize the instantiation of individual things.

// A ThingFactory returns a Thing struct configured with the given ID.
type ThingFactory func(id int64) interface{}

// The createShowHandler function is a factory function for creating a handler
// which uses the getThing factory function to obtain an instance of a
// thing to use when generating a view.
func createShowHandler(getThing ThingFactory) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		id, err := strconv.ParseInt(vars["id"], 10, 64)

		if err != nil {
			errorHandler(w, err)
			return
		}

		thing := getThing(id)
		err = db.First(&thing, id).Error

		if err != nil {
			errorHandler(w, err)
		}

		if thing1.ID > 0 {
			jsonHeaders(w, http.StatusOK)
			if err := json.NewEncoder(w).Encode(thing1); err != nil {
				errorHandler(w, err)
			}
			return
		}

		notFoundHandler(w, r)
	}
}

This function can be used to systematically create routes for a given router. For instance, I can create an explicit registry which keeps track of the path for each thing as well as a ThingFactory instance which is used when calling the createShowHandler factory function.

router := mux.NewRouter()
apiRouter := router.PathPrefix("/v1").Subrouter()

registry := []struct {
	path    string
	handler ThingFactory
}{
	{"/thing1/{id}", func(id int64) interface{} { return Thing1{ID: id} }},
	{"/thing2/{id}", func(id int64) interface{} { return Thing2{ID: id} }},
	{"/thing3/{id}", func(id int64) interface{} { return Thing3{ID: id} }},
}

for _, registrant := range registry {
	apiRouter.HandleFunc(registrant.path, createShowHandler(registrant.handler)).Methods("GET")
}

Naturally, you would want to define interfaces for the various interaction points in a program like this to gain more type safety when dealing with a large number of instances. A more robust registry could be implemented that provided an interface for Things to register themselves with.

huangapple
  • 本文由 发表于 2016年10月4日 06:30:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/39841414.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定