A Protobuf Query service processes queries
. Query services are specific to the module in which they are defined, and only process queries
defined within said module. They are called from BaseApp
's Query
method. {synopsis}
- Module Manager {prereq}
- Messages and Queries {prereq}
The querier
type defined in the Cosmos SDK will be deprecated in favor of gRPC Services. It specifies the typical structure of a querier
function:
Let us break it down:
- The
path
is an array ofstring
s that contains the type of the query, and that can also containquery
arguments. Seequeries
for more information. - The
req
itself is primarily used to retrieve arguments if they are too large to fit in thepath
. This is done using theData
field ofreq
. - The
Context
contains all the necessary information needed to process thequery
, as well as a branch of the latest state. It is primarily used by thekeeper
to access the state. - The result
res
returned toBaseApp
, marshalled using the application'scodec
.
When defining a Protobuf Query
service, a QueryServer
interface is generated for each module with all the service methods:
type QueryServer interface {
QueryBalance(context.Context, *QueryBalanceParams) (*types.Coin, error)
QueryAllBalances(context.Context, *QueryAllBalancesParams) (*QueryAllBalancesResponse, error)
}
These custom queries methods should be implemented by a module's keeper, typically in ./keeper/grpc_query.go
. The first parameter of these methods is a generic context.Context
, whereas querier methods generally need an instance of sdk.Context
to read
from the store. Therefore, the Cosmos SDK provides a function sdk.UnwrapSDKContext
to retrieve the sdk.Context
from the provided
context.Context
.
Here's an example implementation for the bank module:
Module legacy querier
s are typically implemented in a ./keeper/querier.go
file inside the module's folder. The module manager is used to add the module's querier
s to the application's queryRouter
via the NewQuerier()
method. Typically, the manager's NewQuerier()
method simply calls a NewQuerier()
method defined in keeper/querier.go
, which looks like the following:
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) {
switch path[0] {
case QueryType1:
return queryType1(ctx, path[1:], req, keeper)
case QueryType2:
return queryType2(ctx, path[1:], req, keeper)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint: %s", types.ModuleName, path[0])
}
}
}
This simple switch returns a querier
function specific to the type of the received query
. At this point of the query lifecycle, the first element of the path
(path[0]
) contains the type of the query. The following elements are either empty or contain arguments needed to process the query.
The querier
functions themselves are pretty straighforward. They generally fetch a value or values from the state using the keeper
. Then, they marshall the value(s) using the codec
and return the []byte
obtained as result.
For a deeper look at querier
s, see this example implementation of a querier
function from the bank module.
Learn about BeginBlocker
and EndBlocker
{hide}