Golang 存储库模式

Golang repostiory pattern

我尝试在 Go 应用程序(简单的 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# 中找到类似泛型的东西,但没有找到。

谁能帮帮我?

如有误解请见谅,更好的模式可能是这样的:

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

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

目前,对于每种类型的存储库,您基本上都有多个接口,因此除非您要实现多种类型的 RoleRepository,否则您最好不要接口。

拥有通用的 RepositoryRepositoryItem 接口可能会使您的代码在较长的 运行.

中更具可扩展性(更不用说更易于测试)

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

不过,我非常建议您也看看@kostix 的回答。

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

您有三个选择:

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

    1. 使用提供的语句查询数据库。
    2. 迭代结果的行。
    3. 对于每一行,调用提供的函数,其任务是 扫描行。

    在这种情况下,您将拥有一个通用的“查询”功能, 区别仅在于“扫描”功能。

    可能有多种变体,但我怀疑您有想法。

  • 使用 sqlx 包,它基本上是 SQL 驱动的数据库,encoding/json 是 JSON 数据流:它使用反射您要创建和执行的类型 SQL 以填充它们。

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

  • 使用 code generation,这是 拥有“代码模板”(这就是泛型的意义所在)的原生方式。

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

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


我注意到您的代码确实非常不合常理。

没有一个头脑正常的人会在 Go 中实现“存储库模式”,但这没关系,只要它能让你开心——我们都在一定程度上被灌输了我们习以为常的 languages/environments到,但你的 connection := r.provider.GetConnection() 看起来很惊人:Go 的 database/sql 与“流行”的环境和框架截然不同,所以我强烈建议从 this and this.

开始

¹(更新于 2021 年 5 月 31 日)Go 将拥有泛型,因为 the proposal to implement them 已被接受并且实施它们的工作正在进行中。

interface{} 是 Go 中的 "generic type"。我可以想象做这样的事情:

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

得到两个结构 and/or 接口