-
-
Save alexedwards/5cd712192b4831058b21 to your computer and use it in GitHub Desktop.
. | |
├── books | |
│ ├── handlers.go | |
│ └── models.go | |
├── config | |
│ └── db.go | |
└── main.go |
package config | |
import ( | |
"database/sql" | |
_ "github.com/lib/pq" | |
) | |
type Env struct { | |
DB *sql.DB | |
} | |
func NewDB(dataSourceName string) (*sql.DB, error) { | |
db, err := sql.Open("postgres", dataSourceName) | |
if err != nil { | |
return nil, err | |
} | |
err = db.Ping() | |
if err != nil { | |
db.Close() | |
return nil, err | |
} | |
return db, nil | |
} |
package books | |
import ( | |
"bookstore/config" | |
"fmt" | |
"net/http" | |
) | |
func BooksIndex(env *config.Env) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
if r.Method != "GET" { | |
http.Error(w, http.StatusText(405), 405) | |
return | |
} | |
bks, err := AllBooks(env.DB) | |
if err != nil { | |
http.Error(w, http.StatusText(500), 500) | |
return | |
} | |
for _, bk := range bks { | |
fmt.Fprintf(w, "%s, %s, %s, £%.2f\n", bk.Isbn, bk.Title, bk.Author, bk.Price) | |
} | |
}) | |
} |
package main | |
import ( | |
"bookstore/books" | |
"bookstore/config" | |
"log" | |
"net/http" | |
) | |
func main() { | |
db, err := config.NewDB("postgres://user:pass@localhost/bookstore") | |
if err != nil { | |
log.Panic(err) | |
} | |
defer db.Close() | |
env := &config.Env{DB: db} | |
http.Handle("/books", books.BooksIndex(env)) | |
http.ListenAndServe(":3000", nil) | |
} |
package books | |
import ( | |
"database/sql" | |
) | |
type Book struct { | |
Isbn string | |
Title string | |
Author string | |
Price float32 | |
} | |
func AllBooks(db *sql.DB) ([]*Book, error) { | |
rows, err := db.Query("SELECT * FROM books") | |
if err != nil { | |
return nil, err | |
} | |
defer rows.Close() | |
bks := make([]*Book, 0) | |
for rows.Next() { | |
bk := new(Book) | |
err := rows.Scan(&bk.Isbn, &bk.Title, &bk.Author, &bk.Price) | |
if err != nil { | |
return nil, err | |
} | |
bks = append(bks, bk) | |
} | |
if err = rows.Err(); err != nil { | |
return nil, err | |
} | |
return bks, nil | |
} |
Any suggestion on @harlow question? I am also thinking about this question. What if I want to share logger object in middleware and data access functions?
@nkumar15 What is the problem?
This will work since BooksIndex(env)
returns http.Handler
.
And you can pass logger directly to the middleware.
http.Handle("/books", loggerMiddleware(books.BooksIndex(env), env.Logger))
http.ListenAndServe(":3000", nil)
Not clear how (specially where) the db.Close() will be done? I am surprised i don't see defer db.Close() anywhere.
What am i missing?
@paracha3, you can defer the db close operation in the main function, just after error check; since it is the root node where db is instantiated.
a concrete example with logging and middleware would have been nice. I would not want to instantiate the data elements in every package, but rather pass them around once initiated.
Have you seen the interface way? what if I want to have multiple packages in interface way .Should I make a config package as same as here?
as far I can see there is an edge case in error handling in NewDB
function which wasn't covered in this sample, as well as in the book, I'm talking about this code
func NewDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
}
shouldn't it be like this?
func NewDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
db.Close()
}
}()
err = db.Ping()
if err != nil {
return nil, err
}
return db, nil
}
I understand that it's still a low-plausible case when db.Ping
may fail, but still
@avoidik You're right, thanks for spotting that. I've updated the Gist (and the books too) 👍
Nice catch. Although, If the function were more bulky than it was then this style would be beneficial. But as it is, wouldn't it be cleaner to just do
......
......
if err != nil {
db.Close()
return nil, err
}
....
Great example @alexedwards. If you're logging from Middleware how would you handle the call chain so Env is avail in the middleware too?