拥有可模拟的Golang Web应用程序/ api结构 - (依赖注入)

时间:2016-08-19 03:48:37

标签: go dependency-injection

在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)

1 个答案:

答案 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中有效地工作。