-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add low-level dependency injection container API (#9658)
- Loading branch information
Showing
11 changed files
with
309 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,3 +52,5 @@ | |
- x/*/client/**/* | ||
"Type: ADR": | ||
- docs/architecture/**/* | ||
"C:container": | ||
- container/**/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package container | ||
|
||
import "reflect" | ||
|
||
// ConstructorInfo defines a special constructor type that is defined by | ||
// reflection. It should be passed as a value to the Provide function. | ||
// Ex: | ||
// option.Provide(ConstructorInfo{ ... }) | ||
type ConstructorInfo struct { | ||
// In defines the in parameter types to Fn. | ||
In []reflect.Type | ||
|
||
// Out defines the out parameter types to Fn. | ||
Out []reflect.Type | ||
|
||
// Fn defines the constructor function. | ||
Fn func([]reflect.Value) []reflect.Value | ||
|
||
// Location defines the source code location to be used for this constructor | ||
// in error messages. | ||
Location Location | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package container_test | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/cosmos/cosmos-sdk/container" | ||
) | ||
|
||
type KVStoreKey struct { | ||
name string | ||
} | ||
|
||
type ModuleKey string | ||
|
||
type MsgClientA struct { | ||
key ModuleKey | ||
} | ||
|
||
type KeeperA struct { | ||
key KVStoreKey | ||
} | ||
|
||
type KeeperB struct { | ||
key KVStoreKey | ||
msgClientA MsgClientA | ||
} | ||
|
||
type Handler struct { | ||
Handle func() | ||
} | ||
|
||
type Command struct { | ||
Run func() | ||
} | ||
|
||
func ProvideKVStoreKey(scope container.Scope) KVStoreKey { | ||
return KVStoreKey{name: scope.Name()} | ||
} | ||
|
||
func ProvideModuleKey(scope container.Scope) ModuleKey { | ||
return ModuleKey(scope.Name()) | ||
} | ||
|
||
func ProvideMsgClientA(_ container.Scope, key ModuleKey) MsgClientA { | ||
return MsgClientA{key} | ||
} | ||
|
||
type ModuleA struct{} | ||
|
||
func (ModuleA) Provide(key KVStoreKey) (KeeperA, Handler, Command) { | ||
return KeeperA{key}, Handler{}, Command{} | ||
} | ||
|
||
type ModuleB struct{} | ||
|
||
type BDependencies struct { | ||
container.StructArgs | ||
|
||
Key KVStoreKey | ||
A MsgClientA | ||
} | ||
|
||
type BProvides struct { | ||
KeeperB KeeperB | ||
Handler Handler | ||
Commands []Command | ||
} | ||
|
||
func (ModuleB) Provide(dependencies BDependencies) BProvides { | ||
return BProvides{ | ||
KeeperB: KeeperB{ | ||
key: dependencies.Key, | ||
msgClientA: dependencies.A, | ||
}, | ||
Handler: Handler{}, | ||
Commands: []Command{{}, {}}, | ||
} | ||
} | ||
|
||
func TestRun(t *testing.T) { | ||
t.Skip("Expecting this test to fail for now") | ||
require.NoError(t, | ||
container.Run( | ||
func(handlers map[container.Scope]Handler, commands []Command, a KeeperA, b KeeperB) { | ||
// TODO: | ||
// require one Handler for module a and a scopes | ||
// require 3 commands | ||
// require KeeperA have store key a | ||
// require KeeperB have store key b and MsgClientA | ||
}), | ||
container.AutoGroupTypes(reflect.TypeOf(Command{})), | ||
container.OnePerScopeTypes(reflect.TypeOf(Handler{})), | ||
container.Provide( | ||
ProvideKVStoreKey, | ||
ProvideModuleKey, | ||
ProvideMsgClientA, | ||
), | ||
container.ProvideWithScope(container.NewScope("a"), wrapProvideMethod(ModuleA{})), | ||
container.ProvideWithScope(container.NewScope("b"), wrapProvideMethod(ModuleB{})), | ||
) | ||
} | ||
|
||
func wrapProvideMethod(module interface{}) container.ConstructorInfo { | ||
method := reflect.TypeOf(module).Method(0) | ||
methodTy := method.Type | ||
var in []reflect.Type | ||
var out []reflect.Type | ||
|
||
for i := 1; i < methodTy.NumIn(); i++ { | ||
in = append(in, methodTy.In(i)) | ||
} | ||
for i := 0; i < methodTy.NumOut(); i++ { | ||
out = append(out, methodTy.Out(i)) | ||
} | ||
|
||
return container.ConstructorInfo{ | ||
In: in, | ||
Out: out, | ||
Fn: func(values []reflect.Value) []reflect.Value { | ||
values = append([]reflect.Value{reflect.ValueOf(module)}, values...) | ||
return method.Func.Call(values) | ||
}, | ||
Location: container.LocationFromPC(method.Func.Pointer()), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/cosmos/cosmos-sdk/container | ||
|
||
go 1.16 | ||
|
||
require github.com/stretchr/testify v1.7.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package container | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// Location describes the source code location of a dependency injection | ||
// constructor. | ||
type Location interface { | ||
isLocation() | ||
fmt.Stringer | ||
fmt.Formatter | ||
} | ||
|
||
// LocationFromPC builds a Location from a function program counter location, | ||
// such as that returned by reflect.Value.Pointer() or runtime.Caller(). | ||
func LocationFromPC(pc uintptr) Location { | ||
panic("TODO") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package container | ||
|
||
import "reflect" | ||
|
||
// Option is a functional option for a container. | ||
type Option interface { | ||
isOption() | ||
} | ||
|
||
// Provide creates a container option which registers the provided dependency | ||
// injection constructors. Each constructor will be called at most once with the | ||
// exception of scoped constructors which are called at most once per scope | ||
// (see Scope). | ||
func Provide(constructors ...interface{}) Option { | ||
panic("TODO") | ||
} | ||
|
||
// ProvideWithScope creates a container option which registers the provided dependency | ||
// injection constructors that are to be run in the provided scope. Each constructor | ||
// will be called at most once. | ||
func ProvideWithScope(scope Scope, constructors ...interface{}) Option { | ||
panic("TODO") | ||
} | ||
|
||
// AutoGroupTypes creates an option which registers the provided types as types which | ||
// will automatically get grouped together. For a given type T, T and []T can | ||
// be declared as output parameters for constructors as many times within the container | ||
// as desired. All of the provided values for T can be retrieved by declaring an | ||
// []T input parameter. | ||
func AutoGroupTypes(types ...reflect.Type) Option { | ||
panic("TODO") | ||
} | ||
|
||
// OnePerScopeTypes creates an option which registers the provided types as types which | ||
// can have up to one value per scope. All of the values for a one-per-scope type T | ||
// and their respective scopes, can be retrieved by declaring an input parameter map[Scope]T. | ||
func OnePerScopeTypes(types ...reflect.Type) Option { | ||
panic("TODO") | ||
} | ||
|
||
// Error creates an option which causes the dependency injection container to | ||
// fail immediately. | ||
func Error(err error) Option { | ||
panic("TODO") | ||
} | ||
|
||
// Options creates an option which bundles together other options. | ||
func Options(opts ...Option) Option { | ||
panic("TODO") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package container | ||
|
||
import "fmt" | ||
|
||
// Run runs the provided invoker function with values provided by the provided | ||
// options. It is the single entry point for building and running a dependency | ||
// injection container. Invoker should be a function taking one or more | ||
// dependencies from the container, optionally returning an error. | ||
// | ||
// Ex: | ||
// Run(func (x int) error { println(x) }, Provide(func() int { return 1 })) | ||
func Run(invoker interface{}, opts ...Option) error { | ||
return fmt.Errorf("not implemented") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package container | ||
|
||
// Scope is a special type used to define a provider scope. | ||
// | ||
// Special scoped constructors can be used with Provide by declaring a | ||
// constructor with its first input parameter of type Scope. These constructors | ||
// should construct an unique value for each dependency based on scope and will | ||
// be called at most once per scope. | ||
// | ||
// Constructors passed to ProvideWithScope can also declare an input parameter | ||
// of type Scope to retrieve their scope. | ||
type Scope interface { | ||
isScope() | ||
|
||
// Name returns the name of the scope which is unique within a container. | ||
Name() string | ||
} | ||
|
||
// NewScope creates a new scope with the provided name. Only one scope with a | ||
// given name can be created per container. | ||
func NewScope(name string) Scope { | ||
return &scope{name: name} | ||
} | ||
|
||
type scope struct { | ||
name string | ||
} | ||
|
||
func (s *scope) isScope() {} | ||
|
||
func (s *scope) Name() string { | ||
return s.name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package container | ||
|
||
// StructArgs is a type which can be embedded in another struct to alert the | ||
// container that the fields of the struct are dependency inputs/outputs. That | ||
// is, the container will not look to resolve a value with StructArgs embedded | ||
// directly, but will instead use the struct's fields to resolve or populate | ||
// dependencies. Types with embedded StructArgs can be used in both the input | ||
// and output parameter positions. | ||
type StructArgs struct{} | ||
|
||
func (StructArgs) isStructArgs() {} |