docs: update ADR 057 app wiring (#13736)
* docs: update ADR 057 app wiring * remove ADR 061 ref
This commit is contained in:
parent
709b4e2ca0
commit
1c3e74104a
|
@ -1,12 +1,13 @@
|
||||||
# ADR 057: App Wiring Part I
|
# ADR 057: App Wiring
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
* 2022-05-04: Initial Draft
|
* 2022-05-04: Initial Draft
|
||||||
|
* 2022-08-19: Updates
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
PROPOSED Partially Implemented
|
PROPOSED Implemented
|
||||||
|
|
||||||
## Abstract
|
## Abstract
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ features:
|
||||||
|
|
||||||
* dependency resolution and provision through functional constructors, ex: `func(need SomeDep) (AnotherDep, error)`
|
* dependency resolution and provision through functional constructors, ex: `func(need SomeDep) (AnotherDep, error)`
|
||||||
* dependency injection `In` and `Out` structs which support `optional` dependencies
|
* dependency injection `In` and `Out` structs which support `optional` dependencies
|
||||||
* grouped-dependencies (many-per-container) through the `AutoGroupType` tag interface
|
* grouped-dependencies (many-per-container) through the `ManyPerContainerType` tag interface
|
||||||
* module-scoped dependencies via `ModuleKey`s (where each module gets a unique dependency)
|
* module-scoped dependencies via `ModuleKey`s (where each module gets a unique dependency)
|
||||||
* one-per-module dependencies through the `OnePerModuleType` tag interface
|
* one-per-module dependencies through the `OnePerModuleType` tag interface
|
||||||
* sophisticated debugging information and container visualization via GraphViz
|
* sophisticated debugging information and container visualization via GraphViz
|
||||||
|
@ -63,7 +64,7 @@ Here are some examples of how these would be used in an SDK module:
|
||||||
|
|
||||||
* `StoreKey` could be a module-scoped dependency which is unique per module
|
* `StoreKey` could be a module-scoped dependency which is unique per module
|
||||||
* a module's `AppModule` instance (or the equivalent) could be a `OnePerModuleType`
|
* a module's `AppModule` instance (or the equivalent) could be a `OnePerModuleType`
|
||||||
* CLI commands could be provided with `AutoGroupType`s
|
* CLI commands could be provided with `ManyPerContainerType`s
|
||||||
|
|
||||||
Note that even though dependency resolution is dynamic and based on reflection, which could be considered a pitfall
|
Note that even though dependency resolution is dynamic and based on reflection, which could be considered a pitfall
|
||||||
of this approach, the entire dependency graph should be resolved immediately on app startup and only gets resolved
|
of this approach, the entire dependency graph should be resolved immediately on app startup and only gets resolved
|
||||||
|
@ -94,7 +95,9 @@ message ModuleConfig {
|
||||||
The configuration for every module is itself a protobuf message and modules will be identified and loaded based
|
The configuration for every module is itself a protobuf message and modules will be identified and loaded based
|
||||||
on the protobuf type URL of their config object (ex. `cosmos.bank.module.v1.Module`). Modules are given a unique short `name`
|
on the protobuf type URL of their config object (ex. `cosmos.bank.module.v1.Module`). Modules are given a unique short `name`
|
||||||
to share resources across different versions of the same module which might have a different protobuf package
|
to share resources across different versions of the same module which might have a different protobuf package
|
||||||
versions (ex. `cosmos.bank.module.v2.Module`).
|
versions (ex. `cosmos.bank.module.v2.Module`). All module config objects should define the `cosmos.app.v1alpha1.module`
|
||||||
|
descriptor option which will provide additional useful metadata for the framework and which can also be indexed
|
||||||
|
in module registries.
|
||||||
|
|
||||||
An example app config in YAML might look like this:
|
An example app config in YAML might look like this:
|
||||||
|
|
||||||
|
@ -178,35 +181,36 @@ Ex:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func init() {
|
func init() {
|
||||||
module.Register("cosmos.bank.module.v1.Module",
|
appmodule.Register("cosmos.bank.module.v1.Module",
|
||||||
module.Types(
|
appmodule.Types(
|
||||||
types.Types_tx_proto,
|
types.Types_tx_proto,
|
||||||
types.Types_query_proto,
|
types.Types_query_proto,
|
||||||
types.Types_types_proto,
|
types.Types_types_proto,
|
||||||
),
|
),
|
||||||
module.Provide(
|
appmodule.Provide(
|
||||||
provideBankModule,
|
provideBankModule,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type inputs struct {
|
type Inputs struct {
|
||||||
container.In
|
container.In
|
||||||
|
|
||||||
AuthKeeper auth.Keeper
|
AuthKeeper auth.Keeper
|
||||||
DB ormdb.ModuleDB
|
DB ormdb.ModuleDB
|
||||||
}
|
}
|
||||||
|
|
||||||
type outputs struct {
|
type Outputs struct {
|
||||||
Keeper bank.Keeper
|
Keeper bank.Keeper
|
||||||
Handler app.Handler // app.Handler is a hypothetical type which replaces the current AppModule
|
AppModule appmodule.AppModule
|
||||||
}
|
}
|
||||||
|
|
||||||
func provideBankModule(config types.Module, inputs) (outputs, error) { ... }
|
func ProvideBankModule(config *bankmodulev1.Module, Inputs) (Outputs, error) { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that in this module, a module configuration object *cannot* register different dependency providers based on the
|
Note that in this module, a module configuration object *cannot* register different dependency providers at runtime
|
||||||
configuration. This is intentional because it allows us to know globally which modules provide which dependencies. This
|
based on the configuration. This is intentional because it allows us to know globally which modules provide which
|
||||||
|
dependencies, and it will also allow us to do code generation of the whole app initialization. This
|
||||||
can help us figure out issues with missing dependencies in an app config if the needed modules are loaded at runtime.
|
can help us figure out issues with missing dependencies in an app config if the needed modules are loaded at runtime.
|
||||||
In cases where required modules are not loaded at runtime, it may be possible to guide users to the correct module if
|
In cases where required modules are not loaded at runtime, it may be possible to guide users to the correct module if
|
||||||
through a global Cosmos SDK module registry.
|
through a global Cosmos SDK module registry.
|
||||||
|
@ -241,8 +245,51 @@ So far we have described a system which is largely agnostic to the specifics of
|
||||||
`BaseApp`, etc. A second app wiring ADR will be created which outlines the details of how this app wiring system will
|
`BaseApp`, etc. A second app wiring ADR will be created which outlines the details of how this app wiring system will
|
||||||
be applied to the existing SDK in a way that:
|
be applied to the existing SDK in a way that:
|
||||||
|
|
||||||
1. is as easy to apply to existing modules as possible,
|
### Registration of Inter-Module Hooks
|
||||||
2. while also making it possible to improve existing APIs and minimize long-term technical debt
|
|
||||||
|
Some modules define a hooks interface (ex. `StakingHooks`) which allows one module to call back into another module
|
||||||
|
when certain events happen.
|
||||||
|
|
||||||
|
With the app wiring framework, these hooks interfaces can be defined as a `OnePerModuleType`s and then the module
|
||||||
|
which consumes these hooks can collect these hooks as a map of module name to hook type (ex. `map[string]FooHooks`). Ex:
|
||||||
|
```go
|
||||||
|
func init() {
|
||||||
|
appmodule.Register(
|
||||||
|
&foomodulev1.Module{},
|
||||||
|
appmodule.Invoke(InvokeSetFooHooks),
|
||||||
|
...
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func InvokeSetFooHooks(
|
||||||
|
keeper *keeper.Keeper,
|
||||||
|
fooHooks map[string]FooHooks,
|
||||||
|
) error {
|
||||||
|
for k in sort.Strings(maps.Keys(fooHooks)) {
|
||||||
|
keeper.AddFooHooks(fooHooks[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally, the module consuming hooks can allow app's to define an order for calling these hooks based on module name
|
||||||
|
in its config object.
|
||||||
|
|
||||||
|
An alternative way for registering hooks via reflection was considered where all keeper types are inspected to see if
|
||||||
|
they implement the hook interface by the modules exposing hooks. This has the downsides of:
|
||||||
|
* needing to expose all the keepers of all modules to the module providing hooks,
|
||||||
|
* not allowing for encapsulating hooks on a different type which doesn't expose all keeper methods,
|
||||||
|
* harder to know statically which module expose hooks or are checking for them.
|
||||||
|
|
||||||
|
With the approach proposed here, hooks registration will be obviously observable in `app.go` if `depinject` codegen
|
||||||
|
(described below) is used.
|
||||||
|
|
||||||
|
### Code Generation
|
||||||
|
|
||||||
|
The `depinject` framework will optionally allow the app configuration and dependency injection wiring to be code
|
||||||
|
generated. This will allow:
|
||||||
|
* dependency injection wiring to be inspected as regular go code just like the existing `app.go`,
|
||||||
|
* dependency injection to be opt-in with manual wiring 100% still possible.
|
||||||
|
|
||||||
|
Code generation requires that all providers and invokers and their parameters are exported and in non-internal packages.
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
|
@ -272,8 +319,8 @@ registration paradigms. These two methods can live side-by-side for as long as i
|
||||||
|
|
||||||
## Further Discussions
|
## Further Discussions
|
||||||
|
|
||||||
As mentioned above, a second app wiring ADR will be created to describe more specifics than there is space to go
|
The protobuf type registration system described in this ADR has not been implemented and may need to be reconsidered in
|
||||||
into here. Further discussions will also happen within the Cosmos SDK Framework Working Group and in https://github.com/cosmos/cosmos-sdk/discussions/10582.
|
light of code generation. It may be better to do this type registration with a DI provider.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue