在Go中构建应用程序的最佳方法是什么,这样你仍然可以模拟依赖项并进行测试?
在我的应用中,我试图拥有一个公开功能的内部API(或服务)层。然后我在顶部有一层薄薄的HTTP处理程序来与此服务层进行交互。这样,当我们开始进行RPC通信时,我可以重用相同的服务层 每个问题都封装在它自己的Service结构中,它们都实现了相同的可互换接口。
问题在于不同的服务想要彼此交谈。如果我注入依赖项,那么我可能最终会得到循环依赖项。服务A使用服务B,服务B使用服务A. 作为示例,对应用程序服务的请求将返回所请求的应用程序的所有用户。此外,对用户服务的请求将返回与用户关联的所有应用程序。
我的想法: 1 - 使用依赖注入容器并在初始化时将其传递给每个服务。
2 - 使用工厂并传递周围没有太大差别。
Go方法最好的是什么?
以下是文件+示例代码的示例结构:
/**
.
├── dal
│ ├── application.go
│ └── user.go
├── main.go
├── model
│ ├── application.go
│ └── user.go
├── service
│ ├── application.go
│ └── user.go
└── vendor
*/
package model
type Model interface{} // Generic Model interface so all other Models will inherit from
type UserModel struct{
UserID int
Apps []AppModel
}
type AppModel struct{
AppID int
Users []UserModel
}
// DAL
package dal
import (
"model"
)
type DAL interface{
GetByID( id int) model.Model
GetAll(filters map[string]string) []model.Model
}
type AppDal struct{}
func (dal AppDal)GetByID(id int) model.Model {}
func (dal AppDal)GetAll(filters map[string]string) []model.Model {}
type UserDal struct{}
func (dal UserDal)GetByID(id int) model.Model {}
func (dal UserDal)GetAll(filters map[string]string) []model.Model {}
// Services
package service
import (
"model"
"dal"
)
type Service interface{
GetByID (id int) model.Model
GetAll (filters map[string]string) []model.Model
}
type AppService struct{
dal dal.DAL
}
func (s AppService) GetByID(id int) model.Model{
apps := s.dal.GetByID(id)
// Question: How do you inject the userService here
users := userService.GetAll(map[string]string{"ApplicationID": string(id)})
model.AppModel{Users: users}
return
}
func (s AppService) GetAll (filters map[string]string) []model.Model{}
func NewAppService(dal dal.DAL) {
return AppService{dal:dal}
}
type UserService struct{
dal dal.DAL
}
func (s UserService) GetByID(id int) model.Model{
users := s.dal.GetByID(id)
// Question: How do you inject the appservice here
apps := appService.GetAll(map[string]string{"UserID": string(id)})
model.UserModel{Apps: apps}
return
}
func (s UserService) GetAll (filters map[string]string) []model.Model{}
func NewUserService(dal dal.DAL) {
return UserService{dal:dal}
}
// Main
package main
var appDal = AppDal{}
var userDal = UserDal{}
var appService = NewAppService (appDal)
var userService = NewUserService (userDal)
// Should I put all services in a DI Container and pass the DIC to each service. That does not seem right.
// psuedo code here:
http.OnGet("/applications/:id", appService.GetByID)
http.OnGet("/users/:id", userService.GetByID)
答案 0 :(得分:1)
看一下Ben Johnson关于"构建应用程序促进增长的讨论"。您有循环依赖关系,因为您是以人类逻辑方式构建应用程序,而不是以机器逻辑方式构建。相反或在功能组中组织代码,最好在根目录中对域结构进行分组" main"打包并创建依赖包的包。您必须使用接口来避免这种类型的循环依赖。这在讲话Structuring applications for growth
中有更详细的解释特别是在您的应用中,如果您想实现依赖注入,则会遇到严重的实现错误。您应该完全避免函数中的mutate包或全局对象。 Go不是函数式编程,但是一般来说,尝试避免副作用问题是一种很好的编程方法。例如UserService
type UserService struct{
_dal dal.DAL
}
func (s UserService) GetByID(id int) model.Model{
users := s.dal.GetByID(id)
// Question: How do you inject the appservice here
apps := appService.GetAll(map[string]string{"UserID": string(id)})
model.UserModel{Apps: apps}
return
}
GetById
需要一个实现DAL
接口的appService。你在没有使用的字段中有一个_dal(至少看起来是这样)。在省略func签名*
上的func (s *UserService)
时,您也没有传递对此方法的引用。您没有明确地注入此依赖项,而是使用包对象来访问它,这通常很糟糕。相反,我会以这种方式写或多或少:
type DAL interface{
GetByID( id int) (Model, error)
GetAll(filters map[string]string) ([]Model, error)
}
type UserService struct{
InjectedAppService DAL
}
func (s *UserService) GetByID(id int) (Model, error) {
// Question: How do you inject the appservice here
apps, err := s.InjectedAppService.GetAll(map[string]string{"UserID": string(id)})
if err != nil {
return nil, err
}
model := UserModel{Apps: apps}
return model
}
func NewUserService(appService DAL) {
return UserService{appService:dal}
}
事实上,我不确定通过阅读代码你想要做什么,但现在,GetByID使用一个接口并返回一个接口,这是完美的。这允许您通过实现这些接口来创建模拟对象并轻松地测试此功能。如果它没有改变函数之外的任何东西,那么它更容易测试。
对我来说,似乎你不久前已经完成了从Java或C ++到Go的跳跃,你仍然在思考典型的JEE应用程序。别担心,它发生在每个人身上(我也是),并且需要一些时间才能习惯在Go中有效地工作。