Golang Go语言中请教一个简单的 interface 和 models 组织的问题

写得比较长,🙏

问题是这样的,A 层依赖于 B 层,A 作为使用者想隔离 B 的依赖,定义 interface 的时候该如何设计?

比如:controller 层从 JSON 绑定 model ,后续需要调用 service 层的用户处理逻辑

代码如下:


// package main

// 构造 user service 实例 userService := service.NewUserService(…)

// userApp 依赖 user service userApp := controller.NewUserApp(userService)


// package controller type UserApp struct { svc *service.UserService }

type User struct { … }

func NewUserApp(svc *service.UserService) *UserApp { return &UserApp{svc: svc} }

// handler func (app* UserApp) Add(c Context) error { // controller 层绑定 model var user User if err := bindJSON(c, &user); err != nil { return err }

// model 转换
var svcUser service.User
toSvcUser(user, &svcUser);

// 调用 service 逻辑
return app.svc.AddUser(svcUser)

}


// package service type UserService struct { … }

func NewUserService(…) *UserService { … }

这里为了方便测试 controller ,希望把 service 的依赖作为一个接口,变成下面这样:


// package controller
type IUserService interface {
    AddUser(user xxx) error
}

type UserApp struct { svc IUserService }

func NewUserApp(svc IUserService) *UserApp { return &UserApp{svc: svc} }

我的疑问是这里的接口应该是下面的哪一种:


方法 1:

type IUserService interface { // 使用自己包的类型 AddUser(u User) }

方法 2:

type IUserService interface { // 使用依赖包的类型 AddUser(u service.User) }

方法 3:

另开一个 models 包。把类型全塞 models 包里面,controller 层和 service 层都依赖于 models 这种做法似乎不推荐吗?

https://rakyll.org/style-packages/

如果是第一种的话,我是不是还需要在 controller 层加一个 adapter 来适配接口:


// package controller
type UserServiceAdapter struct {
    svc *service.UserService
}

// adapter 实现接口 func (adapter *UserServiceAdapter) AddUser(user User) error { // model 转换 var svcUser service.User toSvcUser(user, &svcUser);

// 调用 service 逻辑
return adapter.svc.AddUser(svcUser)

}

func NewUserServiceAdapter(svc *service.UserSvC) *UserServiceAdapter{ return &UserServiceAdapter{ svc: svc } }

// package // 构造 user service 实例 userService := service.NewUserService(…) userApp := controller.NewUserApp( // 创建 adapter ,adapter 满足接口 controller.NewUserServiceAdapter(userService) )

相关参考链接:

https://tutorialedge.net/golang/accept-interfaces-return-structs/

https://github.com/golang/go/wiki/CodeReviewComments


Golang Go语言中请教一个简单的 interface 和 models 组织的问题

更多关于Golang Go语言中请教一个简单的 interface 和 models 组织的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

抛砖引玉,谈谈我的个人意见
AddUser 依赖的 User ,不属于 controller 也不属于 service ;我常用的做法是把 User 放到 model 下; controller 负责把 Raw Data 转为业务模型,再传给 service
model 下的这些模型,有的模型是数据库模型,那么会顺便为其配置相关 Tag ,包括但不限于 gorm Tag ;有的模型只是功能模型、中间层模型,比较简单
除了 model ,还有一种场景是在 proto 里定义 Message ,这种情况其实和 model 也类似,可以简单视为换了个包名
结论:如果是某一层专用的结构体,会单独放在这一层下;如果是全局通用的结构体,一般放在 model 下;如果是跨项目通用的结构体,放在 proto 下;其中,第二、三种我用的时候没有很强势区分分界线(虽然感觉并不太好)

更多关于Golang Go语言中请教一个简单的 interface 和 models 组织的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢。

我也看到过有人在 model 里面复用一个 user model ,然后加上各种 tag 比如 gorm tag 、json tag 、swagger tag ,一旦复杂了管理起来就麻烦了。感觉还是各种 vo dto 转比较好。

嗯,如果按照 Golang 的习惯可能是不建议你这么抽象的,因为 model 类型多了写起来太麻烦了。但,如果按照你的思路,我觉得还是直接定义到 models 包较好。 另外,要考虑到 Golang interface 机制对代码阅读不友好,尽量不要设计太多的接口,绕来绕去。

重新看了下楼主提到的 https://rakyll.org/style-packages/ ,里边的指导也是有道理的;统一的 model 对 go 来说不太好——这个设计我确实是从 php 带过来的 🐶
按功能划分的话,如文章所述,User 是放在 service 下的,那就应该是和对应的接口定义在一块,看起来也没毛病;那么需要考虑的是会不会出现跨 service 引用模型,实际业务里已经有这种场景存在了,对此,难道就放到 proto 里?似乎可以的样子 🤔

#2 VO 、DTO 这种概念应该是来源于 Java 吧?我比较反感这些。类似的还有接口的定义( I 前缀),接口的实现( IMPL 前缀或后缀)。有段时间我也给接口加了 I 前缀,后来一次迭代里又全部删掉了,随 Go 标准库,根据实际情况用上 er 后缀 😂

如果按照功能垂直分层的话,很可能出现 user 包和别的功能包循环引用的情况。

我后面找些开源代码学习下。

我也拿捏不好 pb 与 model 的界线,如果经常混用,又要写一堆 ToPB() 方法,这个怎么看

我目前是把公共的放 pb ,方便多处引用

在Golang中,interface和models的组织是设计良好代码结构的关键部分。下面是一个简洁的说明,帮助你理解如何组织它们。

首先,interface定义了对象的行为。在Go中,interface是一种类型,它规定了一组方法签名,但不实现它们。通过定义interface,你可以指定任何实现了这些方法的类型都可以被视为该interface类型。

例如,你可能有一个User的interface,它定义了获取用户信息的方法:

type User interface {
    GetName() string
    GetEmail() string
}

然后,你的models(模型)是实现这些interfaces的具体类型。例如,你可以有一个ConcreteUser结构体实现Userinterface:

type ConcreteUser struct {
    Name  string
    Email string
}

func (u ConcreteUser) GetName() string {
    return u.Name
}

func (u ConcreteUser) GetEmail() string {
    return u.Email
}

在组织代码时,通常将interfaces放在单独的包中,比如interfaces包,而将具体的models放在models包中。这样做的好处是,你的代码结构更加清晰,interfaces和models的职责分离,有利于代码的维护和扩展。

此外,通过使用interfaces,你的代码变得更加灵活和可扩展。例如,你可以轻松地添加新的用户类型,只要它们实现了Userinterface即可。

希望这能帮助你理解如何在Go中组织interface和models!如果有更多问题,欢迎继续提问。

回到顶部