github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-057-app-wiring.md (about)

     1  # ADR 057: App Wiring
     2  
     3  ## Changelog
     4  
     5  * 2022-05-04: Initial Draft
     6  * 2022-08-19: Updates
     7  
     8  ## Status
     9  
    10  PROPOSED Implemented
    11  
    12  ## Abstract
    13  
    14  In order to make it easier to build Cosmos SDK modules and apps, we propose a new app wiring system based on
    15  dependency injection and declarative app configurations to replace the current `app.go` code.
    16  
    17  ## Context
    18  
    19  A number of factors have made the SDK and SDK apps in their current state hard to maintain. A symptom of the current
    20  state of complexity is [`simapp/app.go`](https://github.com/cosmos/cosmos-sdk/blob/c3edbb22cab8678c35e21fe0253919996b780c01/simapp/app.go)
    21  which contains almost 100 lines of imports and is otherwise over 600 lines of mostly boilerplate code that is
    22  generally copied to each new project. (Not to mention the additional boilerplate which gets copied in `simapp/simd`.)
    23  
    24  The large amount of boilerplate needed to bootstrap an app has made it hard to release independently versioned go
    25  modules for Cosmos SDK modules as described in [ADR 053: Go Module Refactoring](./adr-053-go-module-refactoring.md).
    26  
    27  In addition to being very verbose and repetitive, `app.go` also exposes a large surface area for breaking changes
    28  as most modules instantiate themselves with positional parameters which forces breaking changes anytime a new parameter
    29  (even an optional one) is needed.
    30  
    31  Several attempts were made to improve the current situation including [ADR 033: Internal-Module Communication](./adr-033-protobuf-inter-module-comm.md)
    32  and [a proof-of-concept of a new SDK](https://github.com/allinbits/cosmos-sdk-poc). The discussions around these
    33  designs led to the current solution described here.
    34  
    35  ## Decision
    36  
    37  In order to improve the current situation, a new "app wiring" paradigm has been designed to replace `app.go` which
    38  involves:
    39  
    40  * declaration configuration of the modules in an app which can be serialized to JSON or YAML
    41  * a dependency-injection (DI) framework for instantiating apps from the that configuration
    42  
    43  ### Dependency Injection
    44  
    45  When examining the code in `app.go` most of the code simply instantiates modules with dependencies provided either
    46  by the framework (such as store keys) or by other modules (such as keepers). It is generally pretty obvious given
    47  the context what the correct dependencies actually should be, so dependency-injection is an obvious solution. Rather
    48  than making developers manually resolve dependencies, a module will tell the DI container what dependency it needs
    49  and the container will figure out how to provide it.
    50  
    51  We explored several existing DI solutions in golang and felt that the reflection-based approach in [uber/dig](https://github.com/uber-go/dig)
    52  was closest to what we needed but not quite there. Assessing what we needed for the SDK, we designed and built
    53  the Cosmos SDK [depinject module](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/depinject), which has the following
    54  features:
    55  
    56  * dependency resolution and provision through functional constructors, ex: `func(need SomeDep) (AnotherDep, error)`
    57  * dependency injection `In` and `Out` structs which support `optional` dependencies
    58  * grouped-dependencies (many-per-container) through the `ManyPerContainerType` tag interface
    59  * module-scoped dependencies via `ModuleKey`s (where each module gets a unique dependency)
    60  * one-per-module dependencies through the `OnePerModuleType` tag interface
    61  * sophisticated debugging information and container visualization via GraphViz
    62  
    63  Here are some examples of how these would be used in an SDK module:
    64  
    65  * `StoreKey` could be a module-scoped dependency which is unique per module
    66  * a module's `AppModule` instance (or the equivalent) could be a `OnePerModuleType`
    67  * CLI commands could be provided with `ManyPerContainerType`s
    68  
    69  Note that even though dependency resolution is dynamic and based on reflection, which could be considered a pitfall
    70  of this approach, the entire dependency graph should be resolved immediately on app startup and only gets resolved
    71  once (except in the case of dynamic config reloading which is a separate topic). This means that if there are any
    72  errors in the dependency graph, they will get reported immediately on startup so this approach is only slightly worse
    73  than fully static resolution in terms of error reporting and much better in terms of code complexity.
    74  
    75  ### Declarative App Config
    76  
    77  In order to compose modules into an app, a declarative app configuration will be used. This configuration is based off
    78  of protobuf and its basic structure is very simple:
    79  
    80  ```protobuf
    81  package cosmos.app.v1;
    82  
    83  message Config {
    84    repeated ModuleConfig modules = 1;
    85  }
    86  
    87  message ModuleConfig {
    88    string name = 1;
    89    google.protobuf.Any config = 2;
    90  }
    91  ```
    92  
    93  (See also https://github.com/cosmos/cosmos-sdk/blob/6e18f582bf69e3926a1e22a6de3c35ea327aadce/proto/cosmos/app/v1alpha1/config.proto)
    94  
    95  The configuration for every module is itself a protobuf message and modules will be identified and loaded based
    96  on the protobuf type URL of their config object (ex. `cosmos.bank.module.v1.Module`). Modules are given a unique short `name`
    97  to share resources across different versions of the same module which might have a different protobuf package
    98  versions (ex. `cosmos.bank.module.v2.Module`). All module config objects should define the `cosmos.app.v1alpha1.module`
    99  descriptor option which will provide additional useful metadata for the framework and which can also be indexed
   100  in module registries.
   101  
   102  An example app config in YAML might look like this:
   103  
   104  ```yaml
   105  modules:
   106    - name: baseapp
   107      config:
   108        "@type": cosmos.baseapp.module.v1.Module
   109        begin_blockers: [staking, auth, bank]
   110        end_blockers: [bank, auth, staking]
   111        init_genesis: [bank, auth, staking]
   112    - name: auth
   113      config:
   114        "@type": cosmos.auth.module.v1.Module
   115        bech32_prefix: "foo"
   116    - name: bank
   117      config:
   118        "@type": cosmos.bank.module.v1.Module
   119    - name: staking
   120      config:
   121        "@type": cosmos.staking.module.v1.Module
   122  ```
   123  
   124  In the above example, there is a hypothetical `baseapp` module which contains the information around ordering of
   125  begin blockers, end blockers, and init genesis. Rather than lifting these concerns up to the module config layer,
   126  they are themselves handled by a module which could allow a convenient way of swapping out different versions of
   127  baseapp (for instance to target different versions of tendermint), without needing to change the rest of the config.
   128  The `baseapp` module would then provide to the server framework (which sort of sits outside the ABCI app) an instance
   129  of `abci.Application`.
   130  
   131  In this model, an app is *modules all the way down* and the dependency injection/app config layer is very much
   132  protocol-agnostic and can adapt to even major breaking changes at the protocol layer.
   133  
   134  ### Module & Protobuf Registration
   135  
   136  In order for the two components of dependency injection and declarative configuration to work together as described,
   137  we need a way for modules to actually register themselves and provide dependencies to the container.
   138  
   139  One additional complexity that needs to be handled at this layer is protobuf registry initialization. Recall that
   140  in both the current SDK `codec` and the proposed [ADR 054: Protobuf Semver Compatible Codegen](https://github.com/cosmos/cosmos-sdk/pull/11802),
   141  protobuf types need to be explicitly registered. Given that the app config itself is based on protobuf and
   142  uses protobuf `Any` types, protobuf registration needs to happen before the app config itself can be decoded. Because
   143  we don't know which protobuf `Any` types will be needed a priori and modules themselves define those types, we need
   144  to decode the app config in separate phases:
   145  
   146  1. parse app config JSON/YAML as raw JSON and collect required module type URLs (without doing proto JSON decoding)
   147  2. build a [protobuf type registry](https://pkg.go.dev/google.golang.org/protobuf@v1.28.0/reflect/protoregistry) based
   148     on file descriptors and types provided by each required module
   149  3. decode the app config as proto JSON using the protobuf type registry
   150  
   151  Because in [ADR 054: Protobuf Semver Compatible Codegen](https://github.com/cosmos/cosmos-sdk/pull/11802), each module
   152  might use `internal` generated code which is not registered with the global protobuf registry, this code should provide
   153  an alternate way to register protobuf types with a type registry. In the same way that `.pb.go` files currently have a
   154  `var File_foo_proto protoreflect.FileDescriptor` for the file `foo.proto`, generated code should have a new member
   155  `var Types_foo_proto TypeInfo` where `TypeInfo` is an interface or struct with all the necessary info to register both
   156  the protobuf generated types and file descriptor.
   157  
   158  So a module must provide dependency injection providers and protobuf types, and takes as input its module
   159  config object which uniquely identifies the module based on its type URL.
   160  
   161  With this in mind, we define a global module register which allows module implementations to register themselves
   162  with the following API:
   163  
   164  ```go
   165  // Register registers a module with the provided type name (ex. cosmos.bank.module.v1.Module)
   166  // and the provided options.
   167  func Register(configTypeName protoreflect.FullName, option ...Option) { ... }
   168  
   169  type Option { /* private methods */ }
   170  
   171  // Provide registers dependency injection provider functions which work with the
   172  // cosmos-sdk container module. These functions can also accept an additional
   173  // parameter for the module's config object.
   174  func Provide(providers ...interface{}) Option { ... }
   175  
   176  // Types registers protobuf TypeInfo's with the protobuf registry.
   177  func Types(types ...TypeInfo) Option { ... }
   178  ```
   179  
   180  Ex:
   181  
   182  ```go
   183  func init() {
   184  	appmodule.Register("cosmos.bank.module.v1.Module",
   185  		appmodule.Types(
   186  			types.Types_tx_proto,
   187              types.Types_query_proto,
   188              types.Types_types_proto,
   189  	    ),
   190  	    appmodule.Provide(
   191  			provideBankModule,
   192  	    )
   193  	)
   194  }
   195  
   196  type Inputs struct {
   197  	container.In
   198  	
   199  	AuthKeeper auth.Keeper
   200  	DB ormdb.ModuleDB
   201  }
   202  
   203  type Outputs struct {
   204  	Keeper bank.Keeper
   205  	AppModule appmodule.AppModule
   206  }
   207  
   208  func ProvideBankModule(config *bankmodulev1.Module, Inputs) (Outputs, error) { ... }
   209  ```
   210  
   211  Note that in this module, a module configuration object *cannot* register different dependency providers at runtime
   212  based on the configuration. This is intentional because it allows us to know globally which modules provide which
   213  dependencies, and it will also allow us to do code generation of the whole app initialization. This
   214  can help us figure out issues with missing dependencies in an app config if the needed modules are loaded at runtime.
   215  In cases where required modules are not loaded at runtime, it may be possible to guide users to the correct module if
   216  through a global Cosmos SDK module registry.
   217  
   218  The `*appmodule.Handler` type referenced above is a replacement for the legacy `AppModule` framework, and
   219  described in [ADR 063: Core Module API](./adr-063-core-module-api.md).
   220  
   221  ### New `app.go`
   222  
   223  With this setup, `app.go` might now look something like this:
   224  
   225  ```go
   226  package main
   227  
   228  import (
   229  	// Each go package which registers a module must be imported just for side-effects
   230  	// so that module implementations are registered.
   231  	_ "github.com/cosmos/cosmos-sdk/x/auth/module"
   232  	_ "github.com/cosmos/cosmos-sdk/x/bank/module"
   233  	_ "github.com/cosmos/cosmos-sdk/x/staking/module"
   234  	"github.com/cosmos/cosmos-sdk/core/app"
   235  )
   236  
   237  // go:embed app.yaml
   238  var appConfigYAML []byte
   239  
   240  func main() {
   241  	app.Run(app.LoadYAML(appConfigYAML))
   242  }
   243  ```
   244  
   245  ### Application to existing SDK modules
   246  
   247  So far we have described a system which is largely agnostic to the specifics of the SDK such as store keys, `AppModule`,
   248  `BaseApp`, etc. Improvements to these parts of the framework that integrate with the general app wiring framework
   249  defined here are described in [ADR 063: Core Module API](./adr-063-core-module-api.md).
   250  
   251  ### Registration of Inter-Module Hooks
   252  
   253  ### Registration of Inter-Module Hooks
   254  
   255  Some modules define a hooks interface (ex. `StakingHooks`) which allows one module to call back into another module
   256  when certain events happen.
   257  
   258  With the app wiring framework, these hooks interfaces can be defined as a `OnePerModuleType`s and then the module
   259  which consumes these hooks can collect these hooks as a map of module name to hook type (ex. `map[string]FooHooks`). Ex:
   260  
   261  ```go
   262  func init() {
   263      appmodule.Register(
   264          &foomodulev1.Module{},
   265          appmodule.Invoke(InvokeSetFooHooks),
   266  	    ...
   267      )
   268  }
   269  func InvokeSetFooHooks(
   270      keeper *keeper.Keeper,
   271      fooHooks map[string]FooHooks,
   272  ) error {
   273  	for k in sort.Strings(maps.Keys(fooHooks)) {
   274  		keeper.AddFooHooks(fooHooks[k])
   275      }
   276  }
   277  ```
   278  
   279  Optionally, the module consuming hooks can allow app's to define an order for calling these hooks based on module name
   280  in its config object.
   281  
   282  An alternative way for registering hooks via reflection was considered where all keeper types are inspected to see if
   283  they implement the hook interface by the modules exposing hooks. This has the downsides of:
   284  
   285  * needing to expose all the keepers of all modules to the module providing hooks,
   286  * not allowing for encapsulating hooks on a different type which doesn't expose all keeper methods,
   287  * harder to know statically which module expose hooks or are checking for them.
   288  
   289  With the approach proposed here, hooks registration will be obviously observable in `app.go` if `depinject` codegen
   290  (described below) is used.
   291  
   292  ### Code Generation
   293  
   294  The `depinject` framework will optionally allow the app configuration and dependency injection wiring to be code
   295  generated. This will allow:
   296  
   297  * dependency injection wiring to be inspected as regular go code just like the existing `app.go`,
   298  * dependency injection to be opt-in with manual wiring 100% still possible.
   299  
   300  Code generation requires that all providers and invokers and their parameters are exported and in non-internal packages.
   301  
   302  ### Module Semantic Versioning
   303  
   304  When we start creating semantically versioned SDK modules that are in standalone go modules, a state machine breaking
   305  change to a module should be handled as follows:
   306  - the semantic major version should be incremented, and
   307  - a new semantically versioned module config protobuf type should be created.
   308  
   309  For instance, if we have the SDK module for bank in the go module `cosmossdk.io/x/bank` with the module config type
   310  `cosmos.bank.module.v1.Module`, and we want to make a state machine breaking change to the module, we would:
   311  - create a new go module `cosmossdk.io/x/bank/v2`,
   312  - with the module config protobuf type `cosmos.bank.module.v2.Module`.
   313  
   314  This _does not_ mean that we need to increment the protobuf API version for bank. Both modules can support
   315  `cosmos.bank.v1`, but `cosmossdk.io/x/bank/v2` will be a separate go module with a separate module config type.
   316  
   317  This practice will eventually allow us to use appconfig to load new versions of a module via a configuration change.
   318  
   319  Effectively, there should be a 1:1 correspondence between a semantically versioned go module and a 
   320  versioned module config protobuf type, and major versioning bumps should occur whenever state machine breaking changes
   321  are made to a module.
   322  
   323  NOTE: SDK modules that are standalone go modules _should not_ adopt semantic versioning until the concerns described in
   324  [ADR 054: Module Semantic Versioning](./adr-054-semver-compatible-modules.md) are
   325  addressed. The short-term solution for this issue was left somewhat unresolved. However, the easiest tactic is
   326  likely to use a standalone API go module and follow the guidelines described in this comment: https://github.com/cosmos/cosmos-sdk/pull/11802#issuecomment-1406815181. For the time-being, it is recommended that
   327  Cosmos SDK modules continue to follow tried and true [0-based versioning](https://0ver.org) until an officially
   328  recommended solution is provided. This section of the ADR will be updated when that happens and for now, this section
   329  should be considered as a design recommendation for future adoption of semantic versioning.
   330  
   331  ## Consequences
   332  
   333  ### Backwards Compatibility
   334  
   335  Modules which work with the new app wiring system do not need to drop their existing `AppModule` and `NewKeeper`
   336  registration paradigms. These two methods can live side-by-side for as long as is needed.
   337  
   338  ### Positive
   339  
   340  * wiring up new apps will be simpler, more succinct and less error-prone
   341  * it will be easier to develop and test standalone SDK modules without needing to replicate all of simapp
   342  * it may be possible to dynamically load modules and upgrade chains without needing to do a coordinated stop and binary
   343    upgrade using this mechanism
   344  * easier plugin integration
   345  * dependency injection framework provides more automated reasoning about dependencies in the project, with a graph visualization.
   346  
   347  ### Negative
   348  
   349  * it may be confusing when a dependency is missing although error messages, the GraphViz visualization, and global
   350    module registration may help with that
   351  
   352  ### Neutral
   353  
   354  * it will require work and education
   355  
   356  ## Further Discussions
   357  
   358  The protobuf type registration system described in this ADR has not been implemented and may need to be reconsidered in
   359  light of code generation. It may be better to do this type registration with a DI provider.
   360  
   361  ## References
   362  
   363  * https://github.com/cosmos/cosmos-sdk/blob/c3edbb22cab8678c35e21fe0253919996b780c01/simapp/app.go
   364  * https://github.com/allinbits/cosmos-sdk-poc
   365  * https://github.com/uber-go/dig
   366  * https://github.com/google/wire
   367  * https://pkg.go.dev/github.com/cosmos/cosmos-sdk/container
   368  * https://github.com/cosmos/cosmos-sdk/pull/11802
   369  * [ADR 063: Core Module API](./adr-063-core-module-api.md)