-
-
Save tsenart/5fc18c659814c078378d to your computer and use it in GitHub Desktop.
package main | |
import ( | |
"net/http" | |
"database/sql" | |
"fmt" | |
"log" | |
"os" | |
) | |
func helloHandler(db *sql.DB) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
var name string | |
// Execute the query. | |
row := db.QueryRow("SELECT myname FROM mytable") | |
if err := row.Scan(&name); err != nil { | |
http.Error(w, err.Error(), 500) | |
return | |
} | |
// Write it back to the client. | |
fmt.Fprintf(w, "hi %s!\n", name) | |
}) | |
} | |
func withMetrics(l *log.Logger, next http.Handler) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
began := time.Now() | |
next.ServeHTTP(w, r) | |
l.Printf("%s %s took %s", r.Method, r.URL, time.Since(began)) | |
}) | |
} | |
func main() { | |
// Open our database connection. | |
db, err := sql.Open("postgres", "…") | |
if err != nil { | |
log.Fatal(err) | |
} | |
// Create our logger | |
logger := log.New(os.Stdout, "", 0) | |
// Register our handler. | |
http.Handle("/hello", helloHandler(db)) | |
// Register our handler with metrics logging | |
http.Handle("/hello_again", withMetrics(logger, helloHandler(db))) | |
http.ListenAndServe(":8080", nil) | |
} |
Why not store db connect to context?
@crossle DB connections are usually opened once, when the app starts, and reused later. Contexts are for things that are started/initialized/created per request or per operation.
@crossle Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
- https://godoc.org/golang.org/x/net/context
This was super helpful to me as I have been learning GoLang. Thanks for posting!
+1 this is good.
@alexedwards I don't think there's an advantage or disadvantage but it does somewhat limit the explosion of types/interfaces. And I'm guessing that in some scenarios you'll want to use your approach in others this one so it's really useful to be aware of both.
and how do you write a test against the handler hellohandler
using this approach?
The main issue with the @tsenart method is if both your handlers and handles live in a separate package from your main database instance. This still requires creating a struct or global variable in order to pass the database to these functions from main. Hence, I don't consider it a very realistic solution for large projects. In terms of package layout and compartmentalization, the method used here looks very nice: https://hackernoon.com/make-yourself-a-go-web-server-with-mongodb-go-on-go-on-go-on-48f394f24e
Diving into best practices to separate DB from code for scalability/flexibility, and came here from ben johnson's post (https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091). Thank you for putting this together. Is there any blog posts on how to write tests against this approach? Thanks!
+1 very helpful
smarter way to organize golang code
and how do you write a test against the handler
hellohandler
using this approach?
Execute helloHandler and put the returned HandlerFunc into a var
and how do you write a test against the handler
hellohandler
using this approach?Execute helloHandler and put the returned HandlerFunc into a var
I guess writing unit test this way is again unrealistic, just imagine if there are thousands of handlers to be tested.
I really like this approach.
Just wondering - Is there any advantage/disadvantage of doing it the above way instead of creating a custom DB type and defining handlers as methods against it? Like so...
It seems to me that the nice thing with this is that the middleware handler (handlerOne) retains the form
func (http.Handler) http.Handler
meaning it can be used nicely for chaining in the Alice style.