Akash IKeeper vs Keeper: Understanding Module Interfaces in the Akash Network Node
The IKeeper interface declares the public contract for cross-module communication and testing, while the concrete Keeper struct implements stateful logic and store manipulation, with NewKeeper returning the interface type to enforce loose coupling.
The akash-network/node repository organizes each Cosmos SDK module using a strict separation between interface and implementation. Understanding the distinction between IKeeper and Keeper is essential for contributing to the codebase or integrating with Akash's decentralized cloud infrastructure, as this pattern appears consistently across modules like take, market, provider, and escrow.
The IKeeper Interface: Public Contracts
In x/take/keeper/keeper.go, the IKeeper interface defines the module's public API without implementation details:
type IKeeper interface {
StoreKey() storetypes.StoreKey
Codec() codec.BinaryCodec
GetParams(ctx sdk.Context) (params types.Params)
SetParams(ctx sdk.Context, params types.Params) error
SubtractFees(ctx sdk.Context, amt sdk.Coin) (sdk.Coin, sdk.Coin, error)
NewQuerier() Querier
GetAuthority() string
}
This contract lists only method signatures, declaring what the module can do while hiding how it does it. Other modules import only this interface, preventing direct coupling to storage internals and allowing the implementation to evolve independently.
The Keeper Struct: Stateful Implementation
Immediately following the interface in the same file, the concrete Keeper struct holds the actual state and dependencies:
type Keeper struct {
skey storetypes.StoreKey
cdc codec.BinaryCodec
authority string
}
The NewKeeper factory function constructs this struct but returns the interface type, encapsulating the concrete details:
func NewKeeper(cdc codec.BinaryCodec, skey storetypes.StoreKey, authority string) IKeeper {
return Keeper{
skey: skey,
cdc: cdc,
authority: authority,
}
}
Unlike the interface, the concrete Keeper contains unexported fields and implements full method bodies that manipulate the Cosmos SDK store, emit events, and perform business logic like fee calculation via internal helpers such as findRate.
Key Differences Between IKeeper and Keeper
| Aspect | IKeeper | Keeper |
|---|---|---|
| Purpose | Declares the public contract for cross-module interaction | Holds state (store keys, codec, authority) and implements logic |
| Definition | Method signatures only; no bodies | Full struct with fields and method implementations |
| Return Type | The type returned by NewKeeper |
The type constructed inside NewKeeper |
| Visibility | Exported for other modules, tests, and mocks | Unexported fields; only interface methods are external |
| Dependencies | None; pure contract | References to other keepers (e.g., EscrowKeeper in market module) |
The interface captures what a module provides, while the concrete keeper determines how it manipulates the blockchain state.
Dependency Injection and Cross-Module Usage
Modules interact through interface types rather than concrete imports, enabling loose coupling. For example, if a function requires access to the take module's fee logic, it accepts the interface:
func ProcessOrder(ctx sdk.Context, takeKeeper takekeeper.IKeeper) {
params := takeKeeper.GetParams(ctx)
fees, remainder, err := takeKeeper.SubtractFees(ctx, payment)
// Business logic proceeds without importing concrete keeper
}
Because the parameter is takekeeper.IKeeper, the calling module does not depend on the take module's internal storage layout or initialization logic.
Testing with Mock Keepers
The interface pattern enables unit testing without a full blockchain state. Test files in testutil/cosmos/mocks/ implement IKeeper with deterministic behavior:
type MockTakeKeeper struct {
keeper.Mock
params types.Params
}
func (m *MockTakeKeeper) GetParams(sdk.Context) types.Params {
return m.params
}
func (m *MockTakeKeeper) SubtractFees(_ sdk.Context, amt sdk.Coin) (sdk.Coin, sdk.Coin, error) {
return amt, sdk.NewCoin(amt.Denom, sdk.ZeroInt()), nil
}
This mock satisfies takekeeper.IKeeper, allowing tests to verify cross-module logic without initializing KV stores or codecs.
Module-Specific Interface Variations
While the pattern is consistent, each module's IKeeper reflects its specific responsibilities:
- Take: Focuses on fee management with
SubtractFeesand parameter getters/setters - Provider: Provides CRUD operations like
Create,Update, andWithProvidersfor provider records - Market: Exposes complex lifecycle methods including
CreateOrder,CreateBid, andOnOrderMatched, often holding references toEscrowKeeperfor payment handling - Deployment/Escrow/Cert/Audit: Standard CRUD patterns with module-specific state management through prefix stores
Each implementation resides in its respective x/<module>/keeper/keeper.go file, following the same structural conventions established in the take module.
Summary
- IKeeper defines the public contract for Akash modules, enabling loose coupling and interface-based mocking
- Keeper implements stateful logic with access to store keys, codecs, and authority addresses required for Cosmos SDK operations
- NewKeeper returns the interface type, hiding concrete implementation details and preventing direct field access
- Cross-module communication relies exclusively on interface types, preventing circular dependencies and storage coupling
- Unit tests leverage the interface to inject mocks rather than initializing full keeper dependencies
Frequently Asked Questions
Why does Akash use IKeeper interfaces instead of concrete Keeper structs?
The interface pattern decouples modules, allowing developers to change internal storage mechanisms in x/take/keeper/keeper.go without affecting dependent modules. It also enables test mocking, as unit tests can implement IKeeper with deterministic behavior rather than spinning up a full Cosmos SDK application with database dependencies.
Where is the IKeeper interface defined in the Akash codebase?
Each module defines its IKeeper interface at the top of the keeper/keeper.go file, immediately followed by the concrete Keeper struct. For example, see x/take/keeper/keeper.go lines 12-21 for the interface definition and lines 23-39 for the struct and factory function.
How does NewKeeper hide the concrete implementation?
The NewKeeper factory function declares a return type of IKeeper (the interface) but constructs and returns a Keeper struct internally. This means callers receive an interface value and cannot access the unexported fields (skey, cdc, authority) of the concrete struct, enforcing encapsulation and preventing direct store manipulation outside the defined methods.
Can the IKeeper interface vary between different Akash modules?
Yes, while all modules follow the same naming convention (IKeeper for interface, Keeper for struct), the method set varies based on module responsibility. The take module exposes fee-related methods like SubtractFees, while the market module includes lifecycle methods like CreateOrder and OnOrderMatched, reflecting each module's specific domain logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →