'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?


Solution 1:[1]

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
}

Solution 2:[2]

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

Solution 3:[3]

Maybe can't. Because go dose not support genrics before go 1.18.

I think extract dao layer maybe a anti-pattern if you want to use hook.

Imagine that:

// pkg models
type User
type UserChangeLog

func (user *User) afterUpdate(tx) error () {
  // if you want to record user changes
  userChangeLogDao().insert(user)
}
// pkg dao
func (userDao) update() {}
func (userChangeLogDao) insert(User){}

it cause import cycle between dao and model

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Arthur Chaloin
Solution 2 gudgl
Solution 3