3
\$\begingroup\$

First of all I am a newbie in Go. I wrote a CRUD REST API that consists of handlers, a database layer and so on, and it contains a lot of code duplication. I see some patterns here, and I know how to reuse code in some other languages like JS or Java using generics or dynamic types.

What are my options in Go? Note that I don't want to use interface{}.

Here is my userdb CRUD set of function that create, update and so on.

package userdb

import (
    "github.com/atsman/interviewr-go/models"
    "github.com/op/go-logging"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

var log = logging.MustGetLogger("db.user")

func GetUserC(db *mgo.Database) *mgo.Collection {
    return db.C(models.CollectionUsers)
}

func Create(db *mgo.Database, user *models.User) error {
    return GetUserC(db).Insert(user)
}

func Update(db *mgo.Database, id *bson.ObjectId, user *map[string]interface{}) (error, *models.User) {
    var updatedUser = models.User{}
    err := GetUserC(db).UpdateId(id, bson.M{
        "$set": user,
    })
    if err != nil {
        return err, &updatedUser
    }

    err = GetUserC(db).FindId(id).One(&updatedUser)
    return err, &updatedUser
}

func List(db *mgo.Database, query *bson.M) (error, *[]models.User) {
    var users []models.User
    err := GetUserC(db).Find(bson.M{}).All(&users)
    return err, &users
}

func Delete(db *mgo.Database, id *bson.ObjectId) (error, *models.User) {
    var user = models.User{}
    err := GetUserC(db).FindId(id).One(&user)
    if err != nil {
        return err, &user
    }

    err = GetUserC(db).RemoveId(id)
    if err != nil {
        return err, &user
    }

    return nil, &user
}

And here is my companydb package. Its goals are almost the same except with the Company model.

package companydb

import (
    "github.com/atsman/interviewr-go/models"
    "github.com/op/go-logging"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

var log = logging.MustGetLogger("db.company")

func GetCompanyC(db *mgo.Database) *mgo.Collection {
    return db.C(models.CollectionCompanies)
}

func Create(db *mgo.Database, company *models.Company) error {
    return GetCompanyC(db).Insert(company)
}

func Update(db *mgo.Database, id *bson.ObjectId, user *map[string]interface{}) (error, *models.Company) {
    updatedCompany := models.Company{}
    err := GetCompanyC(db).UpdateId(id, bson.M{
        "$set": updatedCompany,
    })
    if err != nil {
        return err, &updatedCompany
    }
    err = GetCompanyC(db).FindId(id).One(&updatedCompany)
    return err, &updatedCompany
}

func List(db *mgo.Database, query *bson.M) (error, *[]models.Company) {
    var companies []models.Company
    err := GetCompanyC(db).Find(bson.M{}).All(&companies)
    return err, &companies
}

func Delete(db *mgo.Database, id *bson.ObjectId) (error, *models.Company) {
    company := models.Company{}
    err := GetCompanyC(db).FindId(id).One(&company)
    if err != nil {
        return err, &company
    }

    err = GetCompanyC(db).RemoveId(id)
    if err != nil {
        return err, &company
    }

    return nil, &company
}

The same duplication exists in my handlers. There is a lot of stuff like get struct from JSON, check for errors and so on. I am thinking of some generic stuff.

So, I think the question is quite easy for guys with some experience. How can I generalize this? Or I shouldn't care at all, maybe it is not a big deal here?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I am also starting out in go (although I used GAE's Datastore), and have spent some time thinking about this. I considered 3 options:

1 Code Generation

I think code generation would be a solid way to approach this solution. There's even a library starting out called streetCURD for PostgreSQL. Code generation seems like a common way to do things in go. There is also a golang code generation tool called gen.

2 Don't Generalize

Depending on how many times you will copy and paste this, it may not be worth for you to generalize it. However, I find that after a few structs I start doubting this route... and others are not generalizing too: Example1. example2.

3 Interfaces

I ended up using interface{} for somethings (like error checking) which you stated you are not interested in. I felt it made the code more readable, which is my goal.

Looking over your code, it seems like you are interested in a method that return a user or a company- depending in the input, interface{} would be the way to go... (pun not intended).

Since I am just starting out, I'd love to see better responses to this question.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.