依赖注入&测试

时间:2017-08-18 20:05:35

标签: go

我正在开发一个小型Go应用程序,它基本上是各种密码存储(Ansible Vault,Hashicorp Vault,Chef Vault等)的包装器。我的想法是:在我的各种配置脚本中,我可以使用我的Go包装器来获取秘密,如果我们决定在幕后切换密码存储,则不需要在我的项目中更新所有接口。

我正在尝试为此应用程序设置正确的测试,并且这样做,我试图找出注入依赖项的最佳方法。

例如,假设项目名为secrets。我的一个实现是ansible。并且ansible实现需要自己的parser,并且需要打开自己的connection到ansible库,以检索数据。

所以我可能会有以下内容:

package secrets

type PasswordStore interface {
    GetKey(key string) (string, error)
}

func New(backend string, config map[string]interface{}) (PasswordStore, error) {
    switch backend {
    case "ansible":
        return ansible.New(config)
    default:
        return nil, fmt.Errorf("Password store '%s' not supported.", backend)
    }
}


package ansible


type Connection interface {
    open() (string, error)
}

type Ansible struct {
    connection Connection
    contents   map[string]string
}

func New(c map[string]interface{}) (*Ansible, error) {
    conn, err := NewConnection(c["ansible_path"].(string))
    if err != nil {
        return nil, err
    }

    // open connection, parse, etc...   

    a := &Ansible{
        connection: conn,
        contents:   parsedData,
    }

    return a, nil
}

所以这看起来不错,因为secrets包不需要知道ansible包依赖关系(连接),而工厂只是新建了一些配置数据的实例。但是,如果我需要模拟Ansible收到的connection,那么似乎没有一个好方法(除非该配置映射有一个名为mock的连接选项)

另一种选择是放弃工厂,只是汇集secrets包中的所有依赖项,如:

package secrets

type PasswordStore interface {
    GetKey(key string) (string, error)
}

func New(backend string, config map[string]interface{}) (PasswordStore, error) {
    switch backend {
    case "ansible":
        return ansible.New(AnsibleConnection{}, config)
    default:
        return nil, fmt.Errorf("Password store '%s' not supported.", backend)
    }
}

package ansible


// same as before in this file, but with injected dependency ...

func New(connect Connection, c map[string]interface{}) (*Ansible, error) {
    conn, err := connect.NewConnection(c["ansible_path"].(string))
    if err != nil {
        return nil, err
    }

    // open connection, parse, etc...   

    a := &Ansible{
        connection: conn,
        contents:   parsedData,
    }

    return a, nil
}

现在注入依赖项,但似乎secrets需要了解每个实现的每个依赖项。

是否有更合理的方法来构建它,以便secrets知道更少?或者顶级包装是否典型地编排一切?

1 个答案:

答案 0 :(得分:3)

什么决定后端是什么?这应该有助于指导你。我已经做了类似的事情,支持项目中的多个数据库,我所做的基本上是:

  • config包读入配置文件,该文件确定正在使用的后端
  • store包提供通用接口,并具有接受配置并返回实现的功能
  • server包仅引用接口
  • main包读取配置,将其传递给store中的工厂函数,然后在创建时将结果注入服务器

因此,当我创建我的服务器(实际上使用数据存储)时,我将配置传递给store中的工厂函数,它返回一个接口,然后将其注入服务器。关于不同的具体实现,唯一需要知道的是暴露接口和工厂的相同包; serverconfigmain个包将其视为黑匣子。