Gorm: Is it possible to define shared methods for common db operations (e.g get by id)?

Issue

Using go and gorm in a project.

I have created a dao level to wrap the database operations, each table has its own dao type.


Current code

  • Get method from FirstDao for first table and FirstModel:

    func (dao *FirstDao) Get(id uint64) (*model.FirstModel, error) {
    }
    
  • Get method from SecondDao for second table and SecondModel:

    func (dao *SecondDao) Get(id uint64) (*model.SecondModel, error) {
    }
    

What I want to achieve

Wondering is it possible to write a BaseDao in go, with a single Get() method, so that I don’t have to write this code 2 times.

This is very easy in Java, but since go is very different, and don’t support real inheritance (I guess), not sure is this possible.


What I have tried

  • Define a Model interface, and try to use refection. But failed.
    Main reason: inside the Get() method, it still need an instance of the original specific struct, e.g model.FirstModel{}, I pass it as interface model.Model, and can’t use it as original type.
  • struct embedding.
  • Googling

Questions

  • Is it possible to do this?
  • If not, why?
  • If yes, how?

Solution

If you’re trying to completely bypass writing a Get() method for each DAO, your only solution is to return an interface{} from this method.
However this approach creates two problems :

  • You need to manually cast the interface{} everywhere.
  • You are sacrifying type safety.

The solution I think the best, is to share most of the code by using structure embedding, and write lightweight wrappers for each DAO to convert the unsafe interface{} into a type-safe value.

Example

First create your base DAO with a generic Get() method.
There is no type generics in Go, so you should return an interface{} here.

type BaseDAO struct {}

func (*BaseDAO) Get(id uint64) (interface{}, error) {}

Then, for each type of data, create a specific DAO implementation, embedding BaseDAO :

type FooModel = string

type FooDAO struct {
    // Embbeding BaseDAO by not specifying a name on this field
    // BaseDAO methods can be called from FooDAO instances
    BaseDAO
}

func (foo *FooDAO) Get(id uint64) (FooModel, error) {
    // Call the shared Get() method from BaseDAO.
    // You can see this just like a `super.Get()` call in Java.
    result, _ := foo.BaseDAO.Get(id)
    return result.(FooModel), nil
}

Answered By – Arthur Chaloin

Answer Checked By – Dawn Plyler (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.