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 Oracle中如何从clob类型截取特定字符串后面的字符
  • ¥15 想通过pywinauto自动电机应用程序按钮,但是找不到应用程序按钮信息
  • ¥15 如何在炒股软件中,爬到我想看的日k线
  • ¥15 seatunnel 怎么配置Elasticsearch
  • ¥15 PSCAD安装问题 ERROR: Visual Studio 2013, 2015, 2017 or 2019 is not found in the system.
  • ¥15 (标签-MATLAB|关键词-多址)
  • ¥15 关于#MATLAB#的问题,如何解决?(相关搜索:信噪比,系统容量)
  • ¥500 52810做蓝牙接受端
  • ¥15 基于PLC的三轴机械手程序