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
,否则您最好不要接口。
拥有通用的 Repository
和 RepositoryItem
接口可能会使您的代码在较长的 运行.
中更具可扩展性(更不用说更易于测试)
一个人为的例子可能是(如果我们假设 Repository
与后端模糊相关)实现,例如 MySQLRepository
和 MongoDBRepository
。通过抽象存储库的功能,您可以防止未来的突变。
不过,我非常建议您也看看@kostix 的回答。
不,Go does not have generics并且在可预见的未来也不会有它们¹。
您有三个选择:
重构您的代码,使您有一个接受 SQL 语句和另一个函数的函数,并且:
- 使用提供的语句查询数据库。
- 迭代结果的行。
- 对于每一行,调用提供的函数,其任务是
扫描行。
在这种情况下,您将拥有一个通用的“查询”功能,
区别仅在于“扫描”功能。
可能有多种变体,但我怀疑您有想法。
使用 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)
}
这样你就不必为 IRole
和 ISale
得到两个结构 and/or 接口
我尝试在 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
,否则您最好不要接口。
拥有通用的 Repository
和 RepositoryItem
接口可能会使您的代码在较长的 运行.
一个人为的例子可能是(如果我们假设 Repository
与后端模糊相关)实现,例如 MySQLRepository
和 MongoDBRepository
。通过抽象存储库的功能,您可以防止未来的突变。
不过,我非常建议您也看看@kostix 的回答。
不,Go does not have generics并且在可预见的未来也不会有它们¹。
您有三个选择:
重构您的代码,使您有一个接受 SQL 语句和另一个函数的函数,并且:
- 使用提供的语句查询数据库。
- 迭代结果的行。
- 对于每一行,调用提供的函数,其任务是 扫描行。
在这种情况下,您将拥有一个通用的“查询”功能, 区别仅在于“扫描”功能。
可能有多种变体,但我怀疑您有想法。
使用
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)
}
这样你就不必为 IRole
和 ISale