I'm having trouble with the distinct query in MongoDB.
I can write it in Mongo shell, it works but I don't know how to implement it in Go code.
Here is my Mongo shell code
db.getCollection('company_role_function').distinct("rolecode", {rolecode : {
$in: ['DHBK_ROLE_01','DHBK_ROLE_03' ] },productid:'IOT_Platform'
})
And here is my Go code
1.profile.go
type CompanyRoleFunction struct {
Rolecode string `json:"rolecode"`
Productid string `json:"productid"`
Functioncode string `json:"functioncode"`
Comid string `json:"comid"`
}
repository.go
package repository
import "bitbucket.org/cloud-platform/vnpt-sso-usermgnt/model"
type IProfileRepository interface {
FindRoleByUserProduct(string) (*model.CompanyRoleFunction, error)
}
mongo_driver.go
package repository
import (
"bitbucket.org/cloud-platform/vnpt-sso-usermgnt/model"
"go.mongodb.org/mongo-driver/bson"
"gopkg.in/mgo.v2"
)
type ProfileRepositoryMongo struct {
db *mgo.Database
collection string
}
func NewProfileRepositoryMongo(db *mgo.Database, collection string) *ProfileRepositoryMongo {
return &ProfileRepositoryMongo{
db: db,
collection: collection,
}
}
//I HAVE TROUBLE HERE
func (r *ProfileRepositoryMongo) FindRoleByUserProduct(rolecode arr[]string) (*model.CompanyRoleFunction, error) {
var companyRoleFunction model.CompanyRoleFunction
//I HAVE TROUBLE HERE
err := r.db.C(r.collection).Find(bson.M{"username": username}).One(&companyRoleFunction)
//I HAVE TROUBLE HERE
if err != nil {
return nil, err
}
return &companyRoleFunction, nil
}
Try the below code for distinct in mgo
package main
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"gopkg.in/mgo.v2"
)
type Result struct {
Rolecode string `json:"rolecode"`
Productid string `json:"productid"`
Functioncode string `json:"functioncode"`
Comid string `json:"comid"`
}
type Results []Result
func main() {
//delete1("GV_BMVT")
//update("GV_BMVT")
check()
}
func check() {
session, err := mgo.Dial("mongodb://casuser:Mellon#222.255.102.145:27017/users")
if err != nil {
panic(err)
}
c := session.DB("users").C("company_role_function")
results := []string{}
roleArray := []string{"DHBK_ROLE_01,", "DHBK_ROLE_03"}
err = c.Find(bson.M{"rolecode": bson.M{"$in": roleArray}, "productid": "IOT_Platform"}).Distinct("rolecode", &results)
if err != nil {
panic(err)
}
fmt.Println(results)
}
Related
Back-end return values are not fixed, sometimes:
{"application": {"instance": [{"instanceId": "v1"}, {"instanceId": "v2"}]}}
or sometimes:
{"application": {"instance": {"instanceId": "v"}}}
how should I take out the corresponding instanceId value?
package main
import (
"encoding/json"
"fmt"
)
type Application struct {
Application struct {
Instance json.RawMessage `json:"instance"`
} `json:"application"`
}
func main() {
a := `{"application": {"instance": {"instanceId": "v"}}}`
//a := `{"application": {"instance": [{"instanceId": "v1"}, {"instanceId": "v2"}]}} `
var p Application
errJson := json.Unmarshal([]byte(a), &p)
if errJson != nil {
fmt.Printf("errJson")
}
fmt.Printf("type:%T", p.Application.Instance)
}
Since the 2 value types clash (one a struct another a slice of structs) it gets messy to encapsulate this into a single type even using catch-all solutions like interface{}.
The simplest solution is present two distinct types and marshal into either to see which "works":
func unmarsh(body []byte) (*type1, *type2, error) {
var (
t1 type1
t2 type2
)
err := json.Unmarshal(body, &t1)
if err == nil {
return &t1, nil, nil
}
err = json.Unmarshal(body, &t2)
if err == nil {
return nil, &t2, nil
}
return nil, nil, err
}
and in your example the two types would be:
type type1 struct {
Application struct {
Instance []struct {
InstanceID string `json:"instanceId"`
} `json:"instance"`
} `json:"application"`
}
type type2 struct {
Application struct {
Instance struct {
InstanceID string `json:"instanceId"`
} `json:"instance"`
} `json:"application"`
}
Working example:
https://play.golang.org/p/Kma32gWfghb
A cleaner solution would be a custom unmarshaler:
type Instances []Instance
func (i *Instances) UnmarshalJSON(in []byte) error {
if len(in)>0 && in[0]=='[' {
var a []Instance
if err:=json.Unmarshal(in,&a); err!=nil {
return err
}
*i=a
return nil
}
var s Instance
if err:=json.Unmarshal(in,&s) ; err!=nil {
return err
}
*i=[]Instance{s}
return nil
}
This would unmarshal an object into a slice of 1.
A more compact solution is provided by #mkopriva:
func (i *Instances) UnmarshalJSON(in []byte) error {
if len(in) > 0 && in[0] == '[' {
return json.Unmarshal(in, (*[]Instance)(i))
}
*i = Instances{{}}
return json.Unmarshal(in, &(*i)[0])
}
Can you see why json values dont get saved:
Update: And if you would like to explain why this is downgraded as "Off topic"?
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
type UsedInContext struct {
UsedIn string `json:"usedin"`
ContextName string `json:"contextname"`
}
type ContextData struct {
usedInContext []UsedInContext `json:"usedincontext"`
}
func saveData() {
var jsonBlob = []byte(`{
"usedincontext":
[
{"usedin":"local", "contextname":"desktop"}
]
}`)
usedInContext := UsedInContext{}
err := json.Unmarshal(jsonBlob, &usedInContext)
if err != nil {
}
usedInContextJson, _ := json.Marshal(usedInContext)
err = ioutil.WriteFile("data.json", usedInContextJson, 0644)
fmt.Printf("%+v", usedInContext)
}
I get the following:
{"usedin":"","contextname":""}
You unmarshal your document to the type UsedInContext, while it matches the schema for ContextData:
type ContextData struct {
UsedInContext []UsedInContext `json:"usedincontext"` // exported
}
var data ContextData
json.Unmarshal(jsonBlob, &data)
fmt.Printf("%+v", data)
I'm trying to figure out how I can (using gin) create a struct from an api call
"icon": [
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48": "https://api.figo.me/assets/images/accounts/postbank_48.png",
"60x60": "https://api.figo.me/assets/images/accounts/postbank_60.png",
"72x72": "https://api.figo.me/assets/images/accounts/postbank_72.png",
"84x84": "https://api.figo.me/assets/images/accounts/postbank_84.png",
"96x96": "https://api.figo.me/assets/images/accounts/postbank_96.png",
"120x120": "https://api.figo.me/assets/images/accounts/postbank_120.png",
"144x144": "https://api.figo.me/assets/images/accounts/postbank_144.png",
"192x192": "https://api.figo.me/assets/images/accounts/postbank_192.png",
"256x256": "https://api.figo.me/assets/images/accounts/postbank_256.png"
}
],
into
type CatalogBank struct {
Advice string `json:"advice"`
BankCode string `json:"bank_code"`
BankName string `json:"bank_name"`
BIC string `json:"bic"`
Credentials []struct {
Label string `json:"label"`
Masked bool `json:"masked"`
} `json:"credentials"`
Icon []struct {
} `json:"icon"`
Language []byte `json:"language"`
}
The icon part is just an extract from, but I always get an unmarshall error for this part. How would I have to definde the 'Icon' part in the struct?
This would work
package main
type CatalogBank struct {
Icon []interface{} `json:"icon"`
}
This is a little tricky in Golang because of the non-strict type in the JSON. If that is definitely the format you are going to receive the data in, you should unmarshal to an Interface{} and then parse the interface into a struct that you can use in your Golang
Direct Unmarshalling cannot be done, as the type of each field is not known
type Icon struct{
ImageLink string
ImageLink48 string
// ...
}
type CatalogBank struct {
Advice string `json:"advice"`
IconRaw []interface{} `json:"icon"`
Icon []Icon
//...
}
func UnmarshalIcon(c &CatalogBank, i interface{}):
// first convert it to the top level list
newIcon := Icon{}
listOfIcons := i.([]interface{})
for _, i := range listOfIcons:
switch iT := i.(type) {
case string:
newIcon.ImageLink = iT
case map[string]interface{}:
for smallIconsKey, smallIconLink := range iT {
if smallIconsKey == "48x48"{
newIcon.ImageLink48 = smallIconLink.(string)
}
// and so on
}
var c CatalogBank{}
_ := json.Unmarshal([]byte(your_json), &c)
for _, i := range c.IconRaw:
UnmarshalIcon(&c, i)
Caveat Emptor: I haven't checked above implementation but it should be something like this
You can not use []struct {} for icon, change it to []interface{} instead, or if you want operate on type safe type look at the second solution with cusom unmarshaler
Solution 1
package main
import (
"encoding/json"
"fmt"
"log"
)
type CatalogBank struct {
Advice string `json:"advice"`
BankCode string `json:"bank_code"`
BankName string `json:"bank_name"`
BIC string `json:"bic"`
Credentials []struct {
Label string `json:"label"`
Masked bool `json:"masked"`
} `json:"credentials"`
Icon []interface{} `json:"icon"`
Language []byte `json:"language"`
}
func main() {
data := `
{
"Advice":"abc",
"icon": [
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48": "https://api.figo.me/assets/images/accounts/postbank_48.png",
"60x60": "https://api.figo.me/assets/images/accounts/postbank_60.png",
"72x72": "https://api.figo.me/assets/images/accounts/postbank_72.png",
"84x84": "https://api.figo.me/assets/images/accounts/postbank_84.png",
"96x96": "https://api.figo.me/assets/images/accounts/postbank_96.png",
"120x120": "https://api.figo.me/assets/images/accounts/postbank_120.png",
"144x144": "https://api.figo.me/assets/images/accounts/postbank_144.png",
"192x192": "https://api.figo.me/assets/images/accounts/postbank_192.png",
"256x256": "https://api.figo.me/assets/images/accounts/postbank_256.png"
}
]
}
`
bank := &CatalogBank{}
err := json.Unmarshal([]byte(data), bank)
if err != nil {
log.Fatal(err)
}
for _, icon := range bank.Icon {
fmt.Printf(" %v\n ", icon)
}
}
Solution 2:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Icons struct {
URL string
BySize map[string]string
}
type CatalogBank struct {
Advice string `json:"advice"`
BankCode string `json:"bank_code"`
BankName string `json:"bank_name"`
BIC string `json:"bic"`
Credentials []struct {
Label string `json:"label"`
Masked bool `json:"masked"`
} `json:"credentials"`
Icon *Icons `json:"-,"`
Language []byte `json:"language"`
}
func (p *CatalogBank) Unmarshal(data []byte) error {
type Transient struct {
*CatalogBank
Icon []interface{} `json:"icon"`
}
var transient = &Transient{CatalogBank:p}
err := json.Unmarshal([]byte(data), transient)
if err != nil {
return err
}
p.Icon = &Icons{
BySize: make(map[string]string),
}
if len(transient.Icon) > 0 {
if url, ok := transient.Icon[0].(string); ok {
p.Icon.URL = url
}
if aMap, ok := transient.Icon[1].(map[string]interface{}); ok {
for k, v := range aMap {
p.Icon.BySize[k] = v.(string)
}
}
}
return nil
}
func main() {
data := `
{
"Advice":"abc",
"icon": [
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48": "https://api.figo.me/assets/images/accounts/postbank_48.png",
"60x60": "https://api.figo.me/assets/images/accounts/postbank_60.png",
"72x72": "https://api.figo.me/assets/images/accounts/postbank_72.png",
"84x84": "https://api.figo.me/assets/images/accounts/postbank_84.png",
"96x96": "https://api.figo.me/assets/images/accounts/postbank_96.png",
"120x120": "https://api.figo.me/assets/images/accounts/postbank_120.png",
"144x144": "https://api.figo.me/assets/images/accounts/postbank_144.png",
"192x192": "https://api.figo.me/assets/images/accounts/postbank_192.png",
"256x256": "https://api.figo.me/assets/images/accounts/postbank_256.png"
}
]
}
`
bank := &CatalogBank{}
err := bank.Unmarshal([]byte(data))
if err != nil {
log.Fatal(err)
}
fmt.Printf("advice: %v\n", bank.Advice)
fmt.Printf("icon: %v\n", bank.Icon.URL)
for size, icon := range bank.Icon.BySize {
fmt.Printf("%v => %v\n ",size, icon)
}
}
You can define your icon like this:
package main
import (
"fmt"
"encoding/json"
)
var testIcon = []byte(`{"icon":[
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48":"https://api.figo.me/assets/images/accounts/postbank_48.png"
}]
}`)
func main() {
icon := make(map[string][]interface{})
err := json.Unmarshal(testIcon, &icon)
if err != nil {
panic(err)
}
fmt.Println(icon)
// map[icon:[https://api.figo.me/assets/images/accounts/postbank.png map[48x48:https://api.figo.me/assets/images/accounts/postbank_48.png]]]
}
I want to use custom interface based on go plugin, but I found it's not support.
Definition of filter.Filter
package filter
import (
"net/http"
"github.com/valyala/fasthttp"
)
// Context filter context
type Context interface {
SetStartAt(startAt int64)
SetEndAt(endAt int64)
GetStartAt() int64
GetEndAt() int64
GetProxyServerAddr() string
GetProxyOuterRequest() *fasthttp.Request
GetProxyResponse() *fasthttp.Response
NeedMerge() bool
GetOriginRequestCtx() *fasthttp.RequestCtx
GetMaxQPS() int
ValidateProxyOuterRequest() bool
InBlacklist(ip string) bool
InWhitelist(ip string) bool
IsCircuitOpen() bool
IsCircuitHalf() bool
GetOpenToCloseFailureRate() int
GetHalfTrafficRate() int
GetHalfToOpenSucceedRate() int
GetOpenToCloseCollectSeconds() int
ChangeCircuitStatusToClose()
ChangeCircuitStatusToOpen()
RecordMetricsForRequest()
RecordMetricsForResponse()
RecordMetricsForFailure()
RecordMetricsForReject()
GetRecentlyRequestSuccessedCount(sec int) int
GetRecentlyRequestCount(sec int) int
GetRecentlyRequestFailureCount(sec int) int
}
// Filter filter interface
type Filter interface {
Name() string
Pre(c Context) (statusCode int, err error)
Post(c Context) (statusCode int, err error)
PostErr(c Context)
}
// BaseFilter base filter support default implemention
type BaseFilter struct{}
// Pre execute before proxy
func (f BaseFilter) Pre(c Context) (statusCode int, err error) {
return http.StatusOK, nil
}
// Post execute after proxy
func (f BaseFilter) Post(c Context) (statusCode int, err error) {
return http.StatusOK, nil
}
// PostErr execute proxy has errors
func (f BaseFilter) PostErr(c Context) {
}
This pkg is in my go app project.
load plugin file
package proxy
import (
"errors"
"plugin"
"strings"
"github.com/fagongzi/gateway/pkg/conf"
"github.com/fagongzi/gateway/pkg/filter"
)
var (
// ErrKnownFilter known filter error
ErrKnownFilter = errors.New("unknow filter")
)
const (
// FilterHTTPAccess access log filter
FilterHTTPAccess = "HTTP-ACCESS"
// FilterHeader header filter
FilterHeader = "HEAD" // process header fiter
// FilterXForward xforward fiter
FilterXForward = "XFORWARD"
// FilterBlackList blacklist filter
FilterBlackList = "BLACKLIST"
// FilterWhiteList whitelist filter
FilterWhiteList = "WHITELIST"
// FilterAnalysis analysis filter
FilterAnalysis = "ANALYSIS"
// FilterRateLimiting limit filter
FilterRateLimiting = "RATE-LIMITING"
// FilterCircuitBreake circuit breake filter
FilterCircuitBreake = "CIRCUIT-BREAKE"
// FilterValidation validation request filter
FilterValidation = "VALIDATION"
)
func newFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
if filterSpec.External {
return newExternalFilter(filterSpec)
}
input := strings.ToUpper(filterSpec.Name)
switch input {
case FilterHTTPAccess:
return newAccessFilter(), nil
case FilterHeader:
return newHeadersFilter(), nil
case FilterXForward:
return newXForwardForFilter(), nil
case FilterAnalysis:
return newAnalysisFilter(), nil
case FilterBlackList:
return newBlackListFilter(), nil
case FilterWhiteList:
return newWhiteListFilter(), nil
case FilterRateLimiting:
return newRateLimitingFilter(), nil
case FilterCircuitBreake:
return newCircuitBreakeFilter(), nil
case FilterValidation:
return newValidationFilter(), nil
default:
return nil, ErrKnownFilter
}
}
func newExternalFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
p, err := plugin.Open(filterSpec.ExternalPluginFile)
if err != nil {
return nil, err
}
s, err := p.Lookup("NewExternalFilter")
if err != nil {
return nil, err
}
sf := s.(func() (filter.Filter, error))
return sf()
}
This is the code of load plugin in my go app project
package main
import (
"C"
"strings"
"time"
"github.com/CodisLabs/codis/pkg/utils/log"
"github.com/fagongzi/gateway/pkg/filter"
"github.com/valyala/fasthttp"
)
// AccessFilter record the http access log
// log format: $remoteip "$method $path" $code "$agent" $svr $cost
type AccessFilter struct {
}
// NewExternalFilter create a External filter
func NewExternalFilter() (filter.Filter, error) {
return &AccessFilter{}, nil
}
// Name return name of this filter
func (f *AccessFilter) Name() string {
return "HTTP-ACCESS"
}
// Pre pre process
func (f *AccessFilter) Pre(c filter.Context) (statusCode int, err error) {
return 200, nil
}
// Post execute after proxy
func (f *AccessFilter) Post(c filter.Context) (statusCode int, err error) {
cost := (c.GetStartAt() - c.GetEndAt())
log.Infof("%s %s \"%s\" %d \"%s\" %s %s",
GetRealClientIP(c.GetOriginRequestCtx()),
c.GetOriginRequestCtx().Method(),
c.GetProxyOuterRequest().RequestURI(),
c.GetProxyResponse().StatusCode(),
c.GetOriginRequestCtx().UserAgent(),
c.GetProxyServerAddr(),
time.Duration(cost))
return 200, nil
}
// PostErr post error process
func (f *AccessFilter) PostErr(c filter.Context) {
}
// GetRealClientIP get read client ip
func GetRealClientIP(ctx *fasthttp.RequestCtx) string {
xforward := ctx.Request.Header.Peek("X-Forwarded-For")
if nil == xforward {
return strings.SplitN(ctx.RemoteAddr().String(), ":", 2)[0]
}
return strings.SplitN(string(xforward), ",", 2)[0]
}
This is the definition of plugin, it's in my plugin project. The plugin project and go app project are different projects.
I found errors:
panic: interface conversion: plugin.Symbol is func() (filter.Filter, error), not func() (filter.Filter, error)
You can find code in this project https://github.com/fagongzi/gateway/tree/go18-plugin-support.
filter.Filter is in pkg/filter package.
load plugin file in proxy/factory.go
plugin go file is in another project.
Custom interfaces work just fine.
But one important thing: you can only type assert types from values looked up from plugins that are defined outside of the plugin (you can't refer types defined in plugins). This also applies to each component of "composite types", for example you can only type assert a function type whose parameter and result types are also defined outside of the plugin.
1. With a common package outside of the plugin
One solution is to define the interface in a package outside of the plugin, and both the plugin and your app can import it and refer to it.
Define it in package filter:
package filter
type Filter interface {
Name() string
Age() int
}
The plugin is in package pq and imports package filter:
package main
import (
"fmt"
"filter"
)
type plgFilter struct{}
func (plgFilter) Name() string { return "Bob" }
func (plgFilter) Age() int { return 23 }
func GetFilter() (f filter.Filter, err error) {
f = plgFilter{}
fmt.Printf("[plugin GetFilter] Returning filter: %T %v\n", f, f)
return
}
And the main app that also imports (the same) package filter, loads the plugin, looks up GetFilter(), calls it and also uses the returned Filter:
package main
import (
"fmt"
"filter"
"plugin"
)
func main() {
p, err := plugin.Open("pg/pg.so")
if err != nil {
panic(err)
}
GetFilter, err := p.Lookup("GetFilter")
if err != nil {
panic(err)
}
filter, err := GetFilter.(func() (filter.Filter, error))()
fmt.Printf("GetFilter result: %T %v %v\n", filter, filter, err)
fmt.Println("\tName:", filter.Name())
fmt.Println("\tAge:", filter.Age())
}
Output:
[plugin GetFilter] Returning filter: main.plgFilter {}
GetFilter result: main.plgFilter {} <nil>
Name: Bob
Age: 23
2. With plugin returning interface{}, and interface defined in main app
Another solution is to have the plugin function return a value of type interface{}. Your main app can define the interface it expects, and it can use type assertion on the interface{} value returned by the plugin.
No filter package this time.
The plugin is in package pq:
package main
import (
"fmt"
)
type plgFilter struct{}
func (plgFilter) Name() string { return "Bob" }
func (plgFilter) Age() int { return 23 }
func GetFilterIface() (f interface{}, err error) {
f = plgFilter{}
fmt.Printf("[plugin GetFilterIface] Returning filter: %T %v\n", f, f)
return
}
And the main app:
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("pg/pg.so")
if err != nil {
panic(err)
}
GetFilterIface, err := p.Lookup("GetFilterIface")
if err != nil {
panic(err)
}
filterIface, err := GetFilterIface.(func() (interface{}, error))()
fmt.Printf("GetFilterIface result: %T %v %v\n", filterIface, filterIface, err)
myfilter := filterIface.(MyFilter)
fmt.Println("\tName:", myfilter.Name())
fmt.Println("\tAge:", myfilter.Age())
}
type MyFilter interface {
Name() string
Age() int
}
Output:
[plugin GetFilterIface] Returning filter: main.plgFilter {}
GetFilterIface result: main.plgFilter {} <nil>
Name: Bob
Age: 23
Also see related question: How do Go plugin dependencies work?
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?
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.
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