Dowemo


Question:

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?


Best Answer:


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.




Copyright © 2011 Dowemo All rights reserved.    Creative Commons   AboutUs