如何在Go中实现依赖注入

huangapple go评论82阅读模式
英文:

How to implement dependency injection in Go

问题

我正在将一个从Play(Scala)迁移到Go的应用程序,并想知道如何实现依赖注入。在Scala中,我使用了蛋糕模式(cake pattern),而在Go中,我实现了一个带有MongoDB实现的DAO接口。

下面是我尝试实现的一种模式,它允许我根据需要更改DAO的实现(例如测试、不同的数据库等):

1. entity.go

package models

import (
    "time"
    "gopkg.in/mgo.v2/bson"
)

type (
    Entity struct {
        Id        bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
        CreatedAt time.Time     `json:"createdAt,omitempty" bson:"createdAt,omitempty"`
        LastUpdate time.Time    `json:"lastUpdate,omitempty" bson:"lastUpdate,omitempty"`
    }
)

2. user.go

package models

import (
    "time"
)

type (
    User struct {
        Entity                  `bson:",inline"`
        Name      string        `json:"name,omitempty" bson:"name,omitempty"`
        BirthDate time.Time     `json:"birthDate,omitempty" bson:"birthDate,omitempty"`
    }
)

3. dao.go

package persistence

type (
    DAO interface {
        Insert(entity interface{}) error
        List(result interface{}, sort string) error
        Find(id string, result interface{}) error
        Update(id string, update interface{}) error
        Remove(id string) error
        Close()
    }

    daoFactory func() DAO
)

var (
    New daoFactory
)

4. mongoDao.go(由于这只是一个示例,DB信息和集合名称是硬编码的)

package persistence

import (
    "fmt"
    "time"
    "errors"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    "github.com/fatih/structs"

    "cmd/server/models"
)

type (
    mongoDAO struct{
        session *mgo.Session
    }
)

func NewMongoDAO() DAO {
    dialInfo := &mgo.DialInfo{
        Addrs:      []string{"localhost:27017"},
        Timeout:    60 * time.Second,
        Database:   "test",
    }

    session, err := mgo.DialWithInfo(dialInfo)

    if err != nil {
        panic(err)
    }

    session.SetMode(mgo.Monotonic, true)

    return &mongoDAO{session}
}

func (dao *mongoDAO) Insert(entity interface{}) error {

    doc := entity.(*models.User)
    doc.Id = bson.NewObjectId()
    doc.CreatedAt = time.Now().UTC()
    doc.LastUpdate = time.Now().UTC()

    return dao.session.DB("test").C("users").Insert(doc)
}

func (dao *mongoDAO) List(result interface{}, sort string) error {
    return dao.session.DB("test").C("users").Find(nil).Sort(sort).All(result)
}

func (dao *mongoDAO) Find(id string, result interface{}) error {
    if !bson.IsObjectIdHex(id) {
        return errors.New(fmt.Sprintf("%s is not a valid hex id", id))
    }

    oid := bson.ObjectIdHex(id)

    return dao.session.DB("test").C("users").FindId(oid).One(result)
}

func (dao *mongoDAO) Update(id string, update interface{}) error {
    if !bson.IsObjectIdHex(id) {
        return errors.New(fmt.Sprintf("%s is not a valid hex id", id))
    }

    oid := bson.ObjectIdHex(id)

    doc := update.(*models.User)
    doc.LastUpdate = time.Now().UTC()

    return dao.session.DB("test").C("users").Update(oid, bson.M{"$set": structs.Map(update)})
}

func (dao *mongoDAO) Remove(id string) error {
    if !bson.IsObjectIdHex(id) {
        return errors.New(fmt.Sprintf("%s is not a valid hex id", id))
    }

    oid := bson.ObjectIdHex(id)

    return dao.session.DB("test").C("users").RemoveId(oid)
}

func (dao *mongoDAO) Close() {
    dao.session.Close()
}

func init() {
    New = NewMongoDAO
}

最后,这是我如何使用上述类型:

5. userController.go

package controllers

import (
    "net/http"
    "github.com/labstack/echo"

    "cmd/server/models"
    "cmd/server/persistence"
)

type (
    UserController struct {
        dao persistence.DAO
    }
)

func NewUserController(dao persistence.DAO) *UserController {
    return &UserController{dao}
}

func (userController *UserController) CreateUser() echo.HandlerFunc {
    return func(context echo.Context) error {
        user := &models.User{}

        if err := context.Bind(user); err != nil {
            return err
        }

        if err := userController.dao.Insert(user); err != nil {
            return err
        }

        return context.JSON(http.StatusCreated, user)
    }
}

func (userController *UserController) UpdateUser() echo.HandlerFunc {
    return func(context echo.Context) error {
        user := &models.User{}

        if err := context.Bind(user); err != nil {
            return err
        }

        id := context.Param("id")
        if err := userController.dao.Update(id, user); err != nil {
            return err
        }

        return context.JSON(http.StatusOK, user)
    }
}

....

上面的代码基本上是正确的...我只有一个问题,在mongoDao.go中的InsertUpdate方法中,编译器强制我将输入的entity转换为特定类型(*models.User),但这样会阻止我拥有一个适用于所有类型的通用DAO组件。我该如何解决这个问题?

英文:

I'm porting an app from Play (Scala) to Go and wondering how to implement dependency injection. In Scala I used the cake pattern, while in Go I implemented a DAO interface along with an implementation for Mongo.

Here below is how I tried to implement a pattern that let me change the DAO implementation as needed (e.g. test, different DB, etc.):

1. entity.go

package models
import (
"time"
"gopkg.in/mgo.v2/bson"
)
type (
Entity struct {
Id        bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
CreatedAt time.Time     `json:"createdAt,omitempty" bson:"createdAt,omitempty"`
LastUpdate time.Time    `json:"lastUpdate,omitempty" bson:"lastUpdate,omitempty"`
}
)

2. user.go

package models
import (
"time"
)
type (
User struct {
Entity                  `bson:",inline"`
Name      string        `json:"name,omitempty" bson:"name,omitempty"`
BirthDate time.Time     `json:"birthDate,omitempty" bson:"birthDate,omitempty"`
}
)

3. dao.go

package persistence
type (
DAO interface {
Insert(entity interface{}) error
List(result interface{}, sort string) error
Find(id string, result interface{}) error
Update(id string, update interface{}) error
Remove(id string) error
Close()
}
daoFactory func() DAO
)
var (
New daoFactory
)

4. mongoDao.go (DB info and collection name are hard-coded since it's just an example)

package persistence
import (
"fmt"
"time"
"errors"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"github.com/fatih/structs"
"cmd/server/models"
)
type (
mongoDAO struct{
session *mgo.Session
}
)
func NewMongoDAO() DAO {
dialInfo := &mgo.DialInfo{
Addrs:      []string{"localhost:27017"},
Timeout:    60 * time.Second,
Database:   "test",
}
session, err := mgo.DialWithInfo(dialInfo)
if err != nil {
panic(err)
}
session.SetMode(mgo.Monotonic, true)
return &mongoDAO{session}
}
func (dao *mongoDAO) Insert(entity interface{}) error {
doc := entity.(*models.User)
doc.Id = bson.NewObjectId()
doc.CreatedAt = time.Now().UTC()
doc.LastUpdate = time.Now().UTC()
return dao.session.DB("test").C("users").Insert(doc)
}
func (dao *mongoDAO) List(result interface{}, sort string) error {
return dao.session.DB("test").C("users").Find(nil).Sort(sort).All(result)
}
func (dao *mongoDAO) Find(id string, result interface{}) error {
if !bson.IsObjectIdHex(id) {
return errors.New(fmt.Sprintf("%s is not a valid hex id", id))
}
oid := bson.ObjectIdHex(id)
return dao.session.DB("test").C("users").FindId(oid).One(result)
}
func (dao *mongoDAO) Update(id string, update interface{}) error {
if !bson.IsObjectIdHex(id) {
return errors.New(fmt.Sprintf("%s is not a valid hex id", id))
}
oid := bson.ObjectIdHex(id)
doc := update.(*models.User)
doc.LastUpdate = time.Now().UTC()
return dao.session.DB("test").C("users").Update(oid, bson.M{"$set": structs.Map(update)})
}
func (dao *mongoDAO) Remove(id string) error {
if !bson.IsObjectIdHex(id) {
return errors.New(fmt.Sprintf("%s is not a valid hex id", id))
}
oid := bson.ObjectIdHex(id)
return dao.session.DB("test").C("users").RemoveId(oid)
}
func (dao *mongoDAO) Close() {
dao.session.Close()
}
func init() {
New = NewMongoDAO
}

Finally, here is how I use the types above:

5. userController.go

package controllers
import (
"net/http"
"github.com/labstack/echo"
"cmd/server/models"
"cmd/server/persistence"
)
type (
UserController struct {
dao persistence.DAO
}
)
func NewUserController(dao persistence.DAO) *UserController {
return &UserController{dao}
}
func (userController *UserController) CreateUser() echo.HandlerFunc {
return func(context echo.Context) error {
user := &models.User{}
if err := context.Bind(user); err != nil {
return err
}
if err := userController.dao.Insert(user); err != nil {
return err
}
return context.JSON(http.StatusCreated, user)
}
}
func (userController *UserController) UpdateUser() echo.HandlerFunc {
return func(context echo.Context) error {
user := &models.User{}
if err := context.Bind(user); err != nil {
return err
}
id := context.Param("id")
if err := userController.dao.Update(id, user); err != nil {
return err
}
return context.JSON(http.StatusOK, user)
}
}
....

The code above is 90% fine... I've just a problem in mongoDao.go with methods Insert and Update where the compiler forces me to cast input entity to a specific type (*models.User), but this prevents me from having a generic DAO component that works for all types. How do I fix this issue?

答案1

得分: 1

这个泛化的DAO接口看起来有些复杂。你在mongo中同时断言为*models.User

doc := entity.(*models.User)

并且执行了:

user := &models.User{}
userController.dao.Insert(user)

当使用你的泛化DAO接口时。为什么不直接更精确地定义接口呢?

DAO interface {
Insert(entity *models.User) error
英文:

This generalization

DAO interface {
Insert(entity interface{}) error

looks over-helming
You both assert to *models.User for mongo

doc := entity.(*models.User)

and do

user := &models.User{}
userController.dao.Insert(user)

when use your generic DAO interface.
Why don't you just define interface more precisely?

DAO interface {
Insert(entity *models.User) error

答案2

得分: 1

创建一个你可以为Entity结构体实现的接口,你觉得怎么样?

type Entitier interface {
    GetEntity() *Entity
}

实现接口的方法只需返回指向自身的指针,然后你可以在DAO的InsertUpdate方法中使用该指针。这样做还有一个额外的好处,就是让你在DAO方法的声明中更加具体。不再只是声明接受任意interface{}类型的参数,而是可以声明接受Entitier类型的参数。

像这样:

func (dao *mongoDAO) Update(id string, update Entitier) error

这里是一个最简化的完整示例,展示了我的意思:

http://play.golang.org/p/lpVs_61mfM

希望这能给你一些想法!一旦你确定了要使用的模式,你可能需要调整Entity/Entitier/GetEntity的命名以符合风格和清晰度要求。

英文:

How about creating an interface that you implement for the Entity struct?

type Entitier interface {
GetEntity() *Entity
}

The implementation would simply return a pointer to itself that you can now use in the Insert and Update methods of your DAO. This would also have the added benefit of letting you be more specific in the declarations of your DAO methods. Instead of simply stating that they take an arbitrary interface{} as argument you could now say that they take an Entitier.

Like so:

func (dao *mongoDAO) Update(id string, update Entitier) error

Here's a minimal complete example of what I mean:

http://play.golang.org/p/lpVs_61mfM

Hope this gives you some ideas! You might want to adjust naming of Entity/Entitier/GetEntity for style and clarity once you've settled on the pattern to use.

huangapple
  • 本文由 发表于 2016年4月17日 20:46:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/36676691.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定