Changelog
- 2020 March 27: Initial Draft
Status
AcceptedContext
This ADR is a continuation of the motivation, design, and context established in ADR 019 and ADR 020, namely, we aim to design the Protocol Buffer migration path for the client-side of the Cosmos SDK. This ADR continues from ADD 020 to specify the encoding of queries.Decision
Custom Query Definition
Modules define custom queries through a protocol buffersservice definition.
These service definitions are generally associated with and used by the
GRPC protocol. However, the protocol buffers specification indicates that
they can be used more generically by any request/response protocol that uses
protocol buffer encoding. Thus, we can use service definitions for specifying
custom ABCI queries and even reuse a substantial amount of the GRPC infrastructure.
Each module with custom queries should define a service canonically named Query:
Handling of Interface Types
Modules that use interface types and need true polymorphism generally force aoneof up to the app-level that provides the set of concrete implementations of
that interface that the app supports. While app’s are welcome to do the same for
queries and implement an app-level query service, it is recommended that modules
provide query methods that expose these interfaces via google.protobuf.Any.
There is a concern on the transaction level that the overhead of Any is too
high to justify its usage. However for queries this is not a concern, and
providing generic module-level queries that use Any does not preclude apps
from also providing app-level queries that return use the app-level oneofs.
A hypothetical example for the gov module would look something like:
Custom Query Implementation
In order to implement the query service, we can reuse the existing gogo protobuf grpc plugin, which for a service namedQuery generates an interface named
QueryServer as below:
context.Context,
whereas querier methods generally need an instance of sdk.Context to read
from the store. Since arbitrary values can be attached to context.Context
using the WithValue and Value methods, the Cosmos SDK should provide a function
sdk.UnwrapSDKContext to retrieve the sdk.Context from the provided
context.Context.
An example implementation of QueryBalance for the bank module as above would
look something like:
Custom Query Registration and Routing
Query server implementations as above would be registered withAppModules using
a new method RegisterQueryService(grpc.Server) which could be implemented simply
as below:
RegisterService(sd *grpc.ServiceDesc, handler interface{})
will be added to the existing baseapp.QueryRouter to add the queries to the custom
query routing table (with the routing method being described below).
The signature for this method matches the existing
RegisterServer method on the GRPC Server type where handler is the custom
query server implementation described above.
GRPC-like requests are routed by the service name (ex. cosmos_sdk.x.bank.v1.Query)
and method name (ex. QueryBalance) combined with /s to form a full
method name (ex. /cosmos_sdk.x.bank.v1.Query/QueryBalance). This gets translated
into an ABCI query as custom/cosmos_sdk.x.bank.v1.Query/QueryBalance. Service handlers
registered with QueryRouter.RegisterService will be routed this way.
Beyond the method name, GRPC requests carry a protobuf encoded payload, which maps naturally
to RequestQuery.Data, and receive a protobuf encoded response or error. Thus
there is a quite natural mapping of GRPC-like rpc methods to the existing
sdk.Query and QueryRouter infrastructure.
This basic specification allows us to reuse protocol buffer service definitions
for ABCI custom queries substantially reducing the need for manual decoding and
encoding in query methods.
GRPC Protocol Support
In addition to providing an ABCI query pathway, we can easily provide a GRPC proxy server that routes requests in the GRPC protocol to ABCI query requests under the hood. In this way, clients could use their host languages’ existing GRPC implementations to make direct queries against Cosmos SDK app’s using theseservice definitions. In order for this server to work, the QueryRouter
on BaseApp will need to expose the service handlers registered with
QueryRouter.RegisterService to the proxy server implementation. Nodes could
launch the proxy server on a separate port in the same process as the ABCI app
with a command-line flag.
REST Queries and Swagger Generation
grpc-gateway is a project that translates REST calls into GRPC calls using special annotations on service methods. Modules that want to expose REST queries should addgoogle.api.http
annotations to their rpc methods as in this example below.
.proto files and the swagger.json
file.
Client Usage
The gogo protobuf grpc plugin generates client interfaces in addition to server interfaces. For theQuery service defined above we would get a QueryClient
interface like:
will receive a new methodQueryConnthat returns aClientConn`
that routes calls to ABCI queries
Clients (such as CLI methods) will then be able to call query methods like this:
Testing
Tests would be able to create a query client directly from keeper andsdk.Context
references using a QueryServerTestHelper as below:
Future Improvements
Consequences
Positive
- greatly simplified querier implementation (no manual encoding/decoding)
- easy query client generation (can use existing grpc and swagger tools)
- no need for REST query implementations
- type safe query methods (generated via grpc plugin)
- going forward, there will be less breakage of query methods because of the backwards compatibility guarantees provided by buf
Negative
- all clients using the existing ABCI/REST queries will need to be refactored for both the new GRPC/REST query paths as well as protobuf/proto-json encoded data, but this is more or less unavoidable in the protobuf refactoring