depinject

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 6, 2024 License: Apache-2.0 Imports: 12 Imported by: 708

README


sidebar_position: 1

Depinject

DISCLAIMER: This is a beta package. The SDK team is actively working on this feature and we are looking for feedback from the community. Please try it out and let us know what you think.

Overview

depinject is a dependency injection (DI) framework for the Cosmos SDK, designed to streamline the process of building and configuring blockchain applications. It works in conjunction with the core/appconfig module to replace the majority of boilerplate code in app.go with a configuration file in Go, YAML, or JSON format.

depinject is particularly useful for developing blockchain applications:

  • With multiple interdependent components, modules, or services. Helping manage their dependencies effectively.
  • That require decoupling of these components, making it easier to test, modify, or replace individual parts without affecting the entire system.
  • That are wanting to simplify the setup and initialisation of modules and their dependencies by reducing boilerplate code and automating dependency management.

By using depinject, developers can achieve:

  • Cleaner and more organised code.

  • Improved modularity and maintainability.

  • A more maintainable and modular structure for their blockchain applications, ultimately enhancing development velocity and code quality.

  • Go Doc

Usage

The depinject framework, based on dependency injection concepts, streamlines the management of dependencies within your blockchain application using its Configuration API. This API offers a set of functions and methods to create easy to use configurations, making it simple to define, modify, and access dependencies and their relationships.

A core component of the Configuration API is the Provide function, which allows you to register provider functions that supply dependencies. Inspired by constructor injection, these provider functions form the basis of the dependency tree, enabling the management and resolution of dependencies in a structured and maintainable manner. Additionally, depinject supports interface types as inputs to provider functions, offering flexibility and decoupling between components, similar to interface injection concepts.

By leveraging depinject and its Configuration API, you can efficiently handle dependencies in your blockchain application, ensuring a clean, modular, and well-organised codebase.

Example:

package main

import (
 "fmt"

 "cosmossdk.io/depinject"
)

type AnotherInt int

func GetInt() int               { return 1 }
func GetAnotherInt() AnotherInt { return 2 }

func main() {
 var (
  x int
  y AnotherInt
 )

 fmt.Printf("Before (%v, %v)\n", x, y)
 depinject.Inject(
  depinject.Provide(
   GetInt,
   GetAnotherInt,
  ),
  &x,
  &y,
 )
 fmt.Printf("After (%v, %v)\n", x, y)
}

In this example, depinject.Provide registers two provider functions that return int and AnotherInt values. The depinject.Inject function is then used to inject these values into the variables x and y.

Provider functions serve as the basis for the dependency tree. They are analysed to identify their inputs as dependencies and their outputs as dependents. These dependents can either be used by another provider function or be stored outside the DI container (e.g., &x and &y in the example above). Provider functions must be exported.

Interface type resolution

depinject supports the use of interface types as inputs to provider functions, which helps decouple dependencies between modules. This approach is particularly useful for managing complex systems with multiple modules, such as the Cosmos SDK, where dependencies need to be flexible and maintainable.

For example, x/bank expects an AccountKeeper interface as input to ProvideModule. SimApp uses the implementation in x/auth, but the modular design allows for easy changes to the implementation if needed.

Consider the following example:

package duck

type Duck interface {
 quack()
}

type AlsoDuck interface {
 quack()
}

type Mallard struct{}
type Canvasback struct{}

func (duck Mallard) quack()    {}
func (duck Canvasback) quack() {}

type Pond struct {
 Duck AlsoDuck
}

And the following provider functions:

func GetMallard() duck.Mallard {
 return Mallard{}
}

func GetPond(duck Duck) Pond {
 return Pond{Duck: duck}
}

func GetCanvasback() Canvasback {
 return Canvasback{}
}

In this example, there's a Pond struct that has a Duck field of type AlsoDuck. The depinject framework can automatically resolve the appropriate implementation when there's only one available, as shown below:

var pond Pond

depinject.Inject(
  depinject.Provide(
   GetMallard,
   GetPond,
  ),
   &pond)

This code snippet results in the Duck field of Pond being implicitly bound to the Mallard implementation because it's the only implementation of the Duck interface in the container.

However, if there are multiple implementations of the Duck interface, as in the following example, you'll encounter an error:

var pond Pond

depinject.Inject(
 depinject.Provide(
  GetMallard,
  GetCanvasback,
  GetPond,
 ),
 &pond)

A specific binding preference for Duck is required.

BindInterface API

In the above situation registering a binding for a given interface binding may look like:

depinject.Inject(
 depinject.Configs(
  depinject.BindInterface(
   "duck/duck.Duck",
   "duck/duck.Mallard",
  ),
  depinject.Provide(
   GetMallard,
   GetCanvasback,
   GetPond,
  ),
 ),
 &pond)

Now depinject has enough information to provide Mallard as an input to APond.

Full example in real app

:::warning When using depinject.Inject, the injected types must be pointers. :::

https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/simapp/app_v2.go#L219-L244

Debugging

Issues with resolving dependencies in the container can be done with logs and Graphviz renderings of the container tree. By default, whenever there is an error, logs will be printed to stderr and a rendering of the dependency graph in Graphviz DOT format will be saved to debug_container.dot.

Here is an example Graphviz rendering of a successful build of a dependency graph: Graphviz Example

Rectangles represent functions, ovals represent types, rounded rectangles represent modules and the single hexagon represents the function which called Build. Black-colored shapes mark functions and types that were called/resolved without an error. Gray-colored nodes mark functions and types that could have been called/resolved in the container but were left unused.

Here is an example Graphviz rendering of a dependency graph build which failed: Graphviz Error Example

Graphviz DOT files can be converted into SVG's for viewing in a web browser using the dot command-line tool, ex:

dot -Tsvg debug_container.dot > debug_container.svg

Many other tools including some IDEs support working with DOT files.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Inject

func Inject(containerConfig Config, outputs ...interface{}) error

Inject builds the container specified by containerConfig and extracts the requested outputs from the container or returns an error. It is the single entry point for building and running a dependency injection container. Each of the values specified as outputs must be pointers to types that can be provided by the container.

Ex:

var x int
Inject(Provide(func() int { return 1 }), &x)

Inject uses the debug mode provided by AutoDebug which means there will be verbose debugging information if there is an error and nothing upon success. Use InjectDebug to configure debug behavior.

func InjectDebug

func InjectDebug(debugOpt DebugOption, config Config, outputs ...interface{}) error

InjectDebug is a version of Inject which takes an optional DebugOption for logging and visualization.

Types

type Config

type Config interface {
	// contains filtered or unexported methods
}

Config is a functional configuration of a container.

func BindInterface

func BindInterface(inTypeName, outTypeName string) Config

BindInterface defines a container configuration for an explicit interface binding of inTypeName to outTypeName in global scope. The example below demonstrates a configuration where the container always provides a Canvasback instance when an interface of type Duck is requested as an input.

BindInterface(

"cosmossdk.io/depinject_test/depinject_test.Duck",
"cosmossdk.io/depinject_test/depinject_test.Canvasback")

func BindInterfaceInModule

func BindInterfaceInModule(moduleName, inTypeName, outTypeName string) Config

BindInterfaceInModule defines a container configuration for an explicit interface binding of inTypeName to outTypeName in the scope of the module with name moduleName. The example below demonstrates a configuration where the container provides a Canvasback instance when an interface of type Duck is requested as an input, but only in the scope of "moduleFoo".

BindInterfaceInModule(

 "moduleFoo",
	"cosmossdk.io/depinject_test/depinject_test.Duck",
	"cosmossdk.io/depinject_test/depinject_test.Canvasback")

func Configs

func Configs(opts ...Config) Config

Configs defines a configuration which bundles together multiple Config definitions.

func Error

func Error(err error) Config

Error defines configuration which causes the dependency injection container to fail immediately.

func Invoke

func Invoke(invokers ...interface{}) Config

Invoke defines a container configuration which registers the provided invoker functions. Each invoker will be called at the end of dependency graph configuration in the order in which it was defined. Invokers may not define output parameters, although they may return an error, and all of their input parameters will be marked as optional so that invokers impose no additional constraints on the dependency graph. Invoker functions should nil-check all inputs. All invoker functions must be declared, exported functions not internal packages and all of their input and output types must also be declared and exported and not in internal packages. Note that generic type parameters will not be checked, but they should also be exported so that codegen is possible.

func InvokeInModule

func InvokeInModule(moduleName string, invokers ...interface{}) Config

InvokeInModule defines a container configuration which registers the provided invoker functions to run in the provided module scope. Each invoker will be called at the end of dependency graph configuration in the order in which it was defined. Invokers may not define output parameters, although they may return an error, and all of their input parameters will be marked as optional so that invokers impose no additional constraints on the dependency graph. Invoker functions should nil-check all inputs. All invoker functions must be declared, exported functions not internal packages and all of their input and output types must also be declared and exported and not in internal packages. Note that generic type parameters will not be checked, but they should also be exported so that codegen is possible.

func Provide

func Provide(providers ...interface{}) Config

Provide defines a container configuration which registers the provided dependency injection providers. Each provider will be called at most once with the exception of module-scoped providers which are called at most once per module (see ModuleKey). All provider functions must be declared, exported functions not internal packages and all of their input and output types must also be declared and exported and not in internal packages. Note that generic type parameters will not be checked, but they should also be exported so that codegen is possible.

func ProvideInModule

func ProvideInModule(moduleName string, providers ...interface{}) Config

ProvideInModule defines container configuration which registers the provided dependency injection providers that are to be run in the named module. Each provider will be called at most once. All provider functions must be declared, exported functions not internal packages and all of their input and output types must also be declared and exported and not in internal packages. Note that generic type parameters will not be checked, but they should also be exported so that codegen is possible.

func Supply

func Supply(values ...interface{}) Config

type DebugOption

type DebugOption interface {
	// contains filtered or unexported methods
}

DebugOption is a functional option for running a container that controls debug logging and visualization output.

func AutoDebug

func AutoDebug() DebugOption

AutoDebug does the same thing as Debug when there is an error and deletes the debug_container.dot if it exists when there is no error. This is the default debug mode of Run.

func Debug

func Debug() DebugOption

Debug is a default debug option which sends log output to stderr, dumps the container in the graphviz DOT and SVG formats to debug_container.dot and debug_container.svg respectively.

func DebugCleanup

func DebugCleanup(cleanup func()) DebugOption

DebugCleanup specifies a clean-up function to be called at the end of processing to clean up any resources that may be used during debugging.

func DebugOptions

func DebugOptions(options ...DebugOption) DebugOption

DebugOptions creates a debug option which bundles together other debug options.

func FileLogger

func FileLogger(filename string) DebugOption

FileLogger is a debug option which routes logging output to a file.

func FileVisualizer

func FileVisualizer(filename string) DebugOption

FileVisualizer is a debug option which dumps a graphviz DOT rendering of the container to the specified file.

func LogVisualizer

func LogVisualizer() DebugOption

LogVisualizer is a debug option which dumps a graphviz DOT rendering of the container to the log.

func Logger

func Logger(logger func(string)) DebugOption

Logger creates an option which provides a logger function which will receive all log messages from the container.

func OnError

func OnError(option DebugOption) DebugOption

OnError is a debug option that allows setting debug options that are conditional on an error happening. Any loggers added error will receive the full dump of logs since the start of container processing.

func OnSuccess

func OnSuccess(option DebugOption) DebugOption

OnSuccess is a debug option that allows setting debug options that are conditional on successful container resolution. Any loggers added on success will receive the full dump of logs since the start of container processing.

func StderrLogger

func StderrLogger() DebugOption

StderrLogger is a debug option which routes logging output to stderr.

func StdoutLogger

func StdoutLogger() DebugOption

StdoutLogger is a debug option which routes logging output to stdout.

func Visualizer

func Visualizer(visualizer func(dotGraph string)) DebugOption

Visualizer creates an option which provides a visualizer function which will receive a rendering of the container in the Graphiz DOT format whenever the container finishes building or fails due to an error. The graph is color-coded to aid debugging with black representing success, red representing an error, and gray representing unused types or functions. Graph rendering should be deterministic for a given version of the container module and container options so that graphs can be used in tests.

type ErrMultipleImplicitInterfaceBindings

type ErrMultipleImplicitInterfaceBindings struct {
	Interface reflect.Type
	Matches   []reflect.Type
}

ErrMultipleImplicitInterfaceBindings defines an error condition where an attempt was made to implicitly bind Interface to a concrete type, but the container was unable to come to a resolution because multiple Matches were found.

func (ErrMultipleImplicitInterfaceBindings) Error

type ErrNoTypeForExplicitBindingFound

type ErrNoTypeForExplicitBindingFound struct {
	Implementation string
	Interface      string
	ModuleName     string
}

ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding was specified from Interface to Implementation but no provider for the requested Implementation was found in the container.

func (ErrNoTypeForExplicitBindingFound) Error

type In

type In struct{}

In can be embedded in another struct to inform the container that the fields of the struct should be treated as dependency inputs. This allows a struct to be used to specify dependencies rather than positional parameters. Unexpected fields will be ignored.

Fields of the struct may support the following tags:

optional	if set to true, the dependency is optional and will
			be set to its default value if not found, rather than causing
			an error

type Location

type Location interface {
	Name() string
	fmt.Stringer
	fmt.Formatter
	// contains filtered or unexported methods
}

func LocationFromCaller

func LocationFromCaller(skip int) Location

func LocationFromPC

func LocationFromPC(pc uintptr) Location

type ManyPerContainerType

type ManyPerContainerType interface {
	// IsManyPerContainerType is a marker function which just indicates that this is a many-per-container type.
	IsManyPerContainerType()
}

ManyPerContainerType marks a type which automatically gets grouped together. For an ManyPerContainerType T, T and []T can be declared as output parameters for providers as many times within the container as desired. All of the provided values for T can be retrieved by declaring an []T input parameter.

type ModuleKey

type ModuleKey struct {
	// contains filtered or unexported fields
}

ModuleKey is a special type used to scope a provider to a "module".

Special module-scoped providers can be used with Provide and ProvideInModule by declaring a provider with an input parameter of type ModuleKey. These providers may construct a unique value of a dependency for each module and will be called at most once per module.

When being used with ProvideInModule, the provider will not receive its own ModuleKey but rather the key of the module requesting the dependency so that modules can provide module-scoped dependencies to other modules.

In order for a module to retrieve their own module key they can define a provider which requires the OwnModuleKey type and DOES NOT require ModuleKey.

func (ModuleKey) Equals

func (k ModuleKey) Equals(other ModuleKey) bool

Equals checks if the module key is equal to another module key. Module keys will be equal only if they have the same name and come from the same ModuleKeyContext.

func (ModuleKey) Name

func (k ModuleKey) Name() string

Name returns the module key's name.

type ModuleKeyContext

type ModuleKeyContext struct {
	// contains filtered or unexported fields
}

ModuleKeyContext defines a context for non-forgeable module keys. All module keys with the same name from the same context should be equal and module keys with the same name but from different contexts should be not equal.

Usage:

moduleKeyCtx := &ModuleKeyContext{}
fooKey := moduleKeyCtx.For("foo")

func (*ModuleKeyContext) For

func (c *ModuleKeyContext) For(moduleName string) ModuleKey

For returns a new or existing module key for the given name within the context.

type OnePerModuleType

type OnePerModuleType interface {
	// IsOnePerModuleType is a marker function just indicates that this is a one-per-module type.
	IsOnePerModuleType()
}

OnePerModuleType marks a type which can have up to one value per module. All of the values for a one-per-module type T and their respective modules, can be retrieved by declaring an input parameter map[string]T.

type Out

type Out struct{}

Out can be embedded in another struct to inform the container that the fields of the struct should be treated as dependency outputs. This allows a struct to be used to specify outputs rather than positional return values.

type OwnModuleKey

type OwnModuleKey ModuleKey

OwnModuleKey is a type which can be used in a module to retrieve its own ModuleKey. It MUST NOT be used together with a ModuleKey dependency.

Directories

Path Synopsis
Package appconfig defines functionality for loading declarative Cosmos SDK app configurations.
Package appconfig defines functionality for loading declarative Cosmos SDK app configurations.
internal
appconfig/testpb
Code generated by protoc-gen-go-pulsar.
Code generated by protoc-gen-go-pulsar.
graphviz
Package graphviz provides some simple types for building graphviz DOT files based on their usage for container debugging.
Package graphviz provides some simple types for building graphviz DOT files based on their usage for container debugging.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL