Skip to content

Commit

Permalink
docs: runtime docs (#22816)
Browse files Browse the repository at this point in the history
(cherry picked from commit aa8266e)

# Conflicts:
#	depinject/README.md
#	runtime/v2/app.go
#	simapp/app.go
#	simapp/simd/cmd/root.go
  • Loading branch information
julienrbrt authored and mergify[bot] committed Dec 11, 2024
1 parent 3ba671f commit 5dfa3ca
Show file tree
Hide file tree
Showing 12 changed files with 943 additions and 597 deletions.
205 changes: 205 additions & 0 deletions depinject/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
---
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](https://pkg.go.dev/cosmossdk.io/depinject)

## 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](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/depinject#Config) 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:

```go
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](https://pkg.go.dev/cosmossdk.io/x/bank/types#AccountKeeper) interface as [input to ProvideModule](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/x/bank/module.go#L208-L260). `SimApp` uses the implementation in `x/auth`, but the modular design allows for easy changes to the implementation if needed.

Consider the following example:

```go
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:

```go
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:

```go
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:

```go
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:

```go
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.
:::

```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/simapp/app_di.go#L187-L206
```

## Debugging

Issues with resolving dependencies in the container can be done with logs and [Graphviz](https://graphviz.org) 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](https://raw.githubusercontent.com/cosmos/cosmos-sdk/ff39d243d421442b400befcd959ec3ccd2525154/depinject/testdata/example.svg)

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](https://raw.githubusercontent.com/cosmos/cosmos-sdk/ff39d243d421442b400befcd959ec3ccd2525154/depinject/testdata/example_error.svg)

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

```txt
dot -Tsvg debug_container.dot > debug_container.svg
```

Many other tools including some IDEs support working with DOT files.
142 changes: 134 additions & 8 deletions docs/build/building-apps/00-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,150 @@ sidebar_position: 1

# What is `runtime`?

The `runtime` package is the Cosmos SDK package that combines the building blocks of your blockchain together. It wires together the modules, the applications, the codecs, and the stores.
The `runtime` package in the Cosmos SDK provides a flexible framework for configuring and managing blockchain applications. It serves as the foundation for creating modular blockchain applications using a declarative configuration approach.

## Overview

The runtime package acts as a wrapper around the `BaseApp` and `ModuleManager`, offering a hybrid approach where applications can be configured both declaratively through configuration files and programmatically through traditional methods.
It is a layer of abstraction between `baseapp` and the application modules that simplifies the process of building a Cosmos SDK application.

## Modules wiring
## Core Components

### App Structure

The runtime App struct contains several key components:

```go
type App struct {
*baseapp.BaseApp
ModuleManager *module.Manager
UnorderedTxManager *unorderedtx.Manager
configurator module.Configurator
config *runtimev1alpha1.Module
storeKeys []storetypes.StoreKey
// ... other fields
}
```

It is the struct that any Cosmos SDK application should embed to leverage the runtime module.

```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/simapp/app_di.go#L61-L62
```

### Configuration

The runtime module is configured using App Wiring. The main configuration object is the [`Module` message](https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/proto/cosmos/app/runtime/v1alpha1/module.proto), which supports the following key settings:

* `app_name`: The name of the application
* `begin_blockers`: List of module names to call during BeginBlock
* `end_blockers`: List of module names to call during EndBlock
* `init_genesis`: Order of module initialization during genesis
* `export_genesis`: Order for exporting module genesis data
* `pre_blockers`: Modules to execute before block processing

Learn more about wiring `runtime` in the [next section](./01-app-go-di.md).

#### Store Configuration

By default, the runtime module uses the module name as the store key.
However it provides a flexible store key configuration through:

* `override_store_keys`: Allows customizing module store keys
* `skip_store_keys`: Specifies store keys to skip during keeper construction

Example configuration:

```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/simapp/v2/app_config.go#L138-L147
```

## Key Features

### 1. BaseApp and other Core SDK components integration

The runtime module integrates with the `BaseApp` and other core SDK components to provide a seamless experience for developers.

The developer only needs to embed the `App` struct in their application to leverage the runtime module.
The configuration of the module manager and other core components is handled internally via the [`AppBuilder`](#4-application-building).

### 2. Module Registration

Runtime has built-in support for [`depinject`-enabled modules](../building-modules/15-depinject.md).
Such modules can be registered through the configuration file (often named `app_config.go`), with no additional code required.

Runtime is responsible for wiring the modules together. It uses `depinject` to inject the dependencies of the modules.
```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/simapp/app_config.go#L210-L215
```

## App wiring
Additionally, the runtime package facilitates manual module registration through the `RegisterModules` method. This is the primary integration point for modules not registered via configuration.

Runtime is the base boilerplate of a Cosmos SDK application.
A user only needs to import `runtime` in their `app.go` and instantiate a `runtime.App`.
:::warning
Even when using manual registration, the module should still be configured in the `Module` message in AppConfig.
:::

```go
func (a *App) RegisterModules(modules ...module.AppModule) error
```

## Services
The SDK recommends using the declarative approach with `depinject` for module registration whenever possible.

Modules have access to a multitude of services that are provided by the runtime.
### 3. Service Registration

Runtime registers all [core services](../../learn/advanced/02-core.md) required by modules.
These services include the `store`, the `event manager`, the `context`, and the `logger`.
As runtime is doing the wiring of modules, it can ensure that the services are scoped to their respective modules.

```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/runtime/module.go#L250-L279
```

Additionally, runtime provides automatic registration of other essential (f.e gRPC routes) services, available to the App:

* AutoCLI Query Service
* Reflection Service
* Custom module services

```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/runtime/builder.go#L74-L77
```

### 4. Application Building

The `AppBuilder` type provides a structured way to build applications:

```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/runtime/builder.go#L22-L29
```

Key building steps:

1. Configuration loading
2. Module registration
3. Service setup
4. Store mounting
5. Router configuration

An application only needs to call `AppBuilder.Build` to create a fully configured application (`runtime.App`).

```go reference
https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-beta.2/runtime/builder.go#L36-L80
```

More information on building applications can be found in the [next section](./02-app-building.md).

## Best Practices

1. **Module Order**: Carefully consider the order of modules in begin_blockers and end_blockers
2. **Store Keys**: Use override_store_keys only when necessary to maintain clarity
3. **Genesis Order**: Maintain correct initialization order in init_genesis
4. **Migration Management**: Use order_migrations to control upgrade paths

### Migration Considerations

When upgrading between versions:

1. Review the migration order specified in `order_migrations`
2. Ensure all required modules are included in the configuration
3. Validate store key configurations
4. Test the upgrade path thoroughly
Loading

0 comments on commit 5dfa3ca

Please sign in to comment.