如何创建将容纳我的数据库和Redis连接的服务类型层

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.