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

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?

2 answers

  • answered 2021-04-08 07:35 gudgl

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

    Just writing my thoughts. Probably this will help you finc your solution

  • answered 2021-04-08 13:55 Arthur Chaloin

    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
    }