我正在开发一个小型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
知道更少?或者顶级包装是否典型地编排一切?
答案 0 :(得分:3)
什么决定后端是什么?这应该有助于指导你。我已经做了类似的事情,支持项目中的多个数据库,我所做的基本上是:
config
包读入配置文件,该文件确定正在使用的后端store
包提供通用接口,并具有接受配置并返回实现的功能server
包仅引用接口main
包读取配置,将其传递给store
中的工厂函数,然后在创建时将结果注入服务器因此,当我创建我的服务器(实际上使用数据存储)时,我将配置传递给store
中的工厂函数,它返回一个接口,然后将其注入服务器。关于不同的具体实现,唯一需要知道的是暴露接口和工厂的相同包; server
,config
和main
个包将其视为黑匣子。