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)