Golang repostiory模式

时间:2015-11-25 17:20:38

标签: go repository-pattern

我尝试在Go app(简单的Web服务)中实现存储库模式,并尝试找到更好的方法来转义代码重复。

这是一个代码

接口是:

type IRoleRepository interface {
    GetAll() ([]Role, error)
}

type ISaleChannelRepository interface {
    GetAll() ([]SaleChannel, error)
}

并实施:

func (r *RoleRepository) GetAll() ([]Role, error) {
        var result []Role
        var err error
        var rows *sql.Rows

        if err != nil {
            return result, err
        }

        connection := r.provider.GetConnection()
        defer connection.Close()

        rows, err = connection.Query("SELECT Id,Name FROM Position")
        defer rows.Close()

        if err != nil {
            return result, err
        }

        for rows.Next() {
            entity := new(Role)
            err = sqlstruct.Scan(entity, rows)

            if err != nil {
                return result, err
            }

            result = append(result, *entity)
        }
        err = rows.Err()
        if err != nil {
            return result, err
        }

        return result, err
    }

    func (r *SaleChannelRepository) GetAll() ([]SaleChannel, error) {
        var result []SaleChannel
        var err error
        var rows *sql.Rows

        if err != nil {
            return result, err
        }

        connection := r.provider.GetConnection()
        defer connection.Close()

        rows, err = connection.Query("SELECT DISTINCT SaleChannel 'Name' FROM Employee")
        defer rows.Close()

        if err != nil {
            return result, err
        }

        for rows.Next() {
            entity := new(SaleChannel)
            err = sqlstruct.Scan(entity, rows)

            if err != nil {
                return result, err
            }

            result = append(result, *entity)
        }
        err = rows.Err()
        if err != nil {
            return result, err
        }

        return result, err
    }

正如您所看到的,差异只有几句话。我尝试从C#中找到类似Generics的东西,但没找到。

任何人都可以帮助我吗?

3 个答案:

答案 0 :(得分:19)

不,Go does not have generics并且在可预见的未来不会有它们。

您有三种选择:

  • 重构您的代码,以便您拥有一个接受SQL语句和另一个函数的函数,并且:

    1. 使用提供的语句查询数据库。
    2. 迭代结果的行。
    3. 对于每一行,调用其任务所在的提供函数 扫描一行。
    4. 在这种情况下,您将拥有一个通用的“查询”功能, 差异仅在于“扫描”功能。

      有可能有几种变化,但我怀疑你有这个想法。

    5. 使用sqlx包基本上是SQL驱动的数据库{J}数据流的encoding/json:它使用类型的反射来创建和执行SQL来填充它们。< / p>

      这样你就可以在另一个层面上获得可重用性:你根本就不会编写样板代码。

    6. 使用code generation Go-native方式拥有“代码模板”(这就是泛型的含义)。

      这样,您(通常)编写一个Go程序,它接受一些输入(以您希望的任何格式),读取并写出一个或多个包含Go代码的文件,然后编译。

      在您非常简单的情况下,您可以从Go函数的模板开始,以及某种表格,该表格将SQL语句映射到要从所选数据创建的类型。

我注意到你的代码确实看起来非常不合时宜。

没有一个正确的人在Go中实现“存储库模式”,但是这很好,只要它让你开心 - 我们都在某种程度上被我们习惯的语言/环境灌输,但是你的connection := r.provider.GetConnection()看起来很惊人:Go database/sql与“热门”环境和框架截然不同,因此我强烈建议您从thisthis开始。

答案 1 :(得分:2)

如果我误解了,请原谅我,但更好的模式可能如下:

type RepositoryItem interface {
    Name() string // for example
}

type Repository interface {
    GetAll() ([]RepositoryItem, error)
}

目前,您基本上为每种类型的存储库都有多个接口,因此,除非您要实现多种类型的RoleRepository,否则您可能没有接口。

从长远来看,拥有通用RepositoryRepositoryItem接口可能会使您的代码更具可扩展性(更不用说更容易测试)。

一个人为的例子可能是(如果我们假设Repository模糊地与后端相关联),例如MySQLRepositoryMongoDBRepository。通过抽象存储库的功能,您可以防止未来的突变。

但我非常建议你看看@ kostix的答案。

答案 2 :(得分:1)

interface{}是Go中的“泛型”。我可以想象做这样的事情:

package main

import "fmt"

type IRole struct {
    RoleId uint
}

type ISaleChannel struct {
    Profitable bool
}

type GenericRepo interface{
    GetAll([]interface{})
}

// conceptual repo to store all Roles and SaleChannels
type Repo struct {
    IRoles        []IRole
    ISaleChannels []ISaleChannel
}

func (r *Repo) GetAll(ifs []interface{}) {

    // database implementation here before type switch 

    for _, v := range ifs {
        switch v := v.(type) {
        default:
                fmt.Printf("unexpected type %T\n", v)
        case IRole:
                fmt.Printf("Role %t\n", v)            
                r.IRoles = append(r.IRoles, v)
        case ISaleChannel:
                fmt.Printf("SaleChannel %d\n", v)
                r.ISaleChannels = append(r.ISaleChannels, v)
        }
    }
}

func main() {

    getter := new(Repo)

    // mock slice
    data := []interface{}{
        IRole{1},
        IRole{2},
        IRole{3},
        ISaleChannel{true},
        ISaleChannel{false},
        IRole{4},
    }

    getter.GetAll(data)

    fmt.Println("IRoles: ", getter.IRoles)
    fmt.Println("ISaleChannels: ", getter.ISales)
}

这样您就不必为IRoleISale

提供两个结构和/或界面