Reputation: 9734
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?
Upvotes: 0
Views: 999
Reputation: 297
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.
Upvotes: 1
Reputation: 8490
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
Upvotes: 1