I am trying to prototype this little golang app and hoping to get some advice on how to go about managing my database and redis connection objects.
I want to create a a 'service layer' that will have all the product related logic, so maybe a ProductService.
I want ProductService to have a reference to both redis and my database client.
What would this ProductService look like roughly, and if I need to create a single instance of this and use it throughout the app do I define it in a var?
func main() {
db, err := gorm.Open("postgres", "host=localhost user=blankman dbname=blank_development sslmode=disable password=")
if err != nil {
log.Fatalf("Got error when connect database, the error is '%v'", err)
}
defer db.Close()
redis := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
pong, err := redis.Ping().Result()
fmt.Println(pong, err)
router := mux.NewRouter()
router.HandleFunc("/products", GetProducts).Methods("GET")
log.Fatal(http.ListenAndServe(":3001", router))
}
My GetProducts handler has your regular signature:
func GetProducts(w http.ResponseWriter, r *http.Request)
How am I suppose to pass in the ProductsService into this handler? It looks like the request/response are somehow automatically passed to this handler by MUX, so unsure how it can get a reference to the ProductService?
Create the product service with the fields you need:
type ProductService struct {
db *gorm.DB
redis *redis.Client
}
Make GetProducts a method of ProductService:
func (s *ProductService) GetProducts(w http.ResponseWriter, r *http.Request) {
// s.db is the database, s.redis is the redis client
}
Initialize ProductService in main. Use method values as handler functions:
s := &ProductService{db: db, redis: redis}
router.HandleFunc("/products", s.GetProducts).Methods("GET")
An alternative to the method value is to use a closure to adapt a function to a handler function:
func (s *ProductService) Handler(fn func(*ProductService, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fn(s, w, r)
}
}
Use
router.HandleFunc("/products", s.Handler(PostPoduct)).Methods("POST")
to register a function like this:
func PostProduct(s *ProductService, w http.ResponseWriter, r *http.Request) {
}
With this second approach, the ProductService layer can be kept separate from the individual handlers.
Another option is to avoid a struct for the service and build your handlers using a closure (with a function that closes over your dependencies) like this:
func MakeGetProductsHander(db *gorm.DB, client *redis.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Use db and client in here...
}
}
Then apply the handler like this:
router.HandleFunc("/products", MakeGetProductsHander(db, client)).Methods("GET")
This injects your dependencies into your handlers in a more explicit and obvious way.
Side note: rename your Redis client variable to client
or something else since you are shadowing the package name by calling it redis
.