doumi7861 2017-08-18 20:05
浏览 42
已采纳

依赖注入与测试

I'm working on a small Go application that's basically a wrapper for various password stores (Ansible Vault, Hashicorp Vault, Chef Vault, etc). The idea is: In my various provisioning scripts, I can use my Go wrapper to grab secrets and if we decide to switch password stores behind the scenes, all of the interfaces don't need to be updated across my projects.

I'm trying to setup proper tests for this application, and in doing so, am trying to figure out the best way to to inject my dependencies.

For example, lets say the project is called secrets. And one of my implementations is ansible. And the ansible implementation needs its own parser and needs to open its own connection to the ansible vault, to retrieve the data.

So I might have the following:

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
}

So this seems nice because the secrets package doesn't need knowledge of the ansible package dependencies (connection), and the factory just new's up the instance with some config data. However, if I need to mock the connection that Ansible receives, there doesn't seem to be a good way to do this (unless that config map had a connection option called mock)

The other option is to abandon the factory, and just assemble all the dependencies from the secrets package, like:

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
}

Now the dependency is injected, but it seems like secrets needs to have knowledge of every dependency for every implementation.

Is there a more logical way to structure this so that secrets knows less? Or is it typical for the top level package to be orchestrating everything?

  • 写回答

1条回答 默认 最新

  • dongwei1855 2017-08-18 20:23
    关注

    What decides what the backend is? That should help guide you. I've done something similar with support for multiple databases on a project, and what I did was basically:

    • config package reads in config file, which determines what backend is being used
    • store package offers the generic interface and has a function that takes a config, and returns an implementation
    • server package references only the interface
    • main package reads the config, passes it to the factory function in store, then injects the result into the server on creation

    So when I create my server (which actually uses the data store), I pass the config to the factory function in store, which returns an interface, and then inject that into the server. The only thing that has to know about the different concrete implementations is the same package that exposes the interface and factory; the server, config, and main packages see it as a black box.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 求差集那个函数有问题,有无佬可以解决
  • ¥15 【提问】基于Invest的水源涵养
  • ¥20 微信网友居然可以通过vx号找到我绑的手机号
  • ¥15 寻一个支付宝扫码远程授权登录的软件助手app
  • ¥15 解riccati方程组
  • ¥15 display:none;样式在嵌套结构中的已设置了display样式的元素上不起作用?
  • ¥15 使用rabbitMQ 消息队列作为url源进行多线程爬取时,总有几个url没有处理的问题。
  • ¥15 Ubuntu在安装序列比对软件STAR时出现报错如何解决
  • ¥50 树莓派安卓APK系统签名
  • ¥65 汇编语言除法溢出问题