Query, Observables & Subscription¶
Your interactions with the DAOstack subgraph will involve working with the following:
- Query: the graphQL queries sent to graphnode to fetch DAOstack data from the subgraph.
- Observable: object representing the stream of data to which one can subscribe.
- Subscription: invokes a given function every time a new value is emitted for the observable.
The entity methods provided by @daostack/client
for querying the subgraph, by themselves do not actually send the query to the server. Instead, each methods returns an Observable to which we can subscribe, which is what actually initiates the query.
Take a look at the following methods that return observable:
1 2 |
|
Now, in order to query the server we must subscribe
1 2 3 4 5 6 7 8 9 10 11 12 |
|
In this guide we will describe how to create & send query and subscribe to the data requested by the query.
Why Subscription¶
Subscriptions will cause the server to send you an update each time the data changes and are useful for composing asynchronous and event-based programs.
By default, subscribing to an observable will do two things:
- send a query to the server to fetch the data
- send a subscription query to the server for update events
How to Query and Subscribe¶
Creating Queries¶
As described in the following sections, you can query the subgraph using either entity methods or raw GraphQL queries.
Entity Methods¶
The entity methods return an Observables which encapsulate some predefined qraphQL queries to fetch Entity data from the subgraph.
1 2 3 4 5 |
|
Raw GraphQL queries¶
To have more control over what gets fetched from the subgraph you can also customize the query. These queries will follow the standard graphQL syntax which is used to query graph explorer directly. Though the query must be wrapped inside the gql tag
1 2 3 4 5 6 |
|
Executing a Query¶
After creating a query, as we did in the previous section, we need to cause the query to be executed, that is, be sent to the graphnode server.
Entity Methods¶
We can subscribe to an observable with {subscribe: false}
parameter (introduced in the subscribing to a query section) for sending a one-time query without subscribing to further updates from the server for result of the query.
1 2 3 4 5 6 7 8 |
|
Note: Refer to the Subscribe to Apollo Cache changes section to understand difference between cache and server update.
Raw GraphQL queries¶
You can submit raw GraphQL queries using the static method arc.sendQuery
. Pass the query designed above as the parameter to sendQuery
.
1 2 3 4 5 |
|
Subscribing to a Query¶
Use Subscriptions to invoke the handler that you supply to run every time a new value is emitted by an observable stream. This is useful to keep the app data updated as the value changes.
Entity methods¶
As we saw the Entity methods do not send the query to the server but return an observable. We must subscribe as follows to send the query as well as subscribe for server updates.
1 2 |
|
Note: Refer to the Subscribe to Apollo Cache changes section to understand difference between cache and server updates.
Raw graphQL queries¶
For even more control over what data is being fetched and subscribed to, you can write explicit queries:
1 2 3 4 5 6 |
|
Optimizing How Subscriptions Use the Cache¶
Since, subscriptions can be expensive, this behavior can be controlled/optimized in several ways. The client library uses Apollo for data management which offers an intelligent caching and declarative approach to data fetching.
- Controlling fetchPolicy: controlling cache interaction.
- Subscribing to Apollo Cache: getting updates from Apollo cache instead of graphnode server.
- FetchAllData and Nested subscription: by querying larger set at top level and subscribing to Apollo cache for nested queries.
Controlling Apollo fetchPolicy¶
We can pass Apollo's fetchPolicy
argument to control how the query interacts with the cache:
- cache-first: default value. Read data from cache first, fetch from network if data is not available in cache.
- cache-and-network: return data from cache first and then always fetch from network to update the cache. It optimizes quick response while also keep cached data updated.
- network-only: will always make a request using network and write data to cache. It optimizes for data consistency with the server.
- cache-only: will never execute a query using your network interface and throw error if data not available in cache.
- no-cache: like
network-only
it will always make a request using your network interface. But, it will not write any data to the cache
1 2 |
|
Subscribe to Apollo Cache changes¶
As we have seen the client library offers two types of subscription that can be controlled by the subscribe
parameter.
- server and cache
{ subscribe: true }
: explicitly ask for the updates from the graph-node server. Update the cache with the results of the query. - only cache
{ subscribe: false }
: do not subscribe to the updates from the server but still subscribe to the Apollo cache changes.
NOTE:
- By default
subscribe
is set to true. - Apollo cache could change as a result of another query which does subscribe to server changes.
e.g.
In q1 we will not subscribe to the updates from network but will still watch changes in the Apollo cache and return updated results if the cache changes.
1 |
|
In q2 we subscribe to server updates. The results of these updates are added to the Apollo cache and the observable in q1 will also get the updates if cache changes.
1 |
|
Use fetchAllData with Nested subscription¶
Most of the Entity methods are implemented in such a way that the queries will fetch (and subscribe to) just as much data as is needed to create the result set. For example, dao.proposals()
will only fetch the proposal IDs. This can be controlled (in a limited way) by setting the parameter fetchAllData
to true
1 |
|
This is useful for cache handling, where it may be useful to have more complete control over what data is being fetched. Consider the following example, which will get the list of proposals from the dao, and then get the state for each of the proposals.
1 2 3 4 5 6 7 |
|
The problem with this pattern is that it is very expensive. The (subscription to) dao.proposals(..)
will send a query and create a subscription and then each of the calls to proposal.state()
will create a new query and a separate subscription.
Consider now the following pattern:
1 2 3 4 5 6 7 |
|
This will resolve two inefficiencies. First of all, the fetchAllData
in the proposals query will make it so that the
dao.proposals
query will fetch (and subscribe to) a much larger query - in particular, it will get all state data for each of the proposals. This means that when prop.state()
is called, it will find all the needed information in the cache (and so it will not send a new query to the server), and we can safely pass it the subscribe: false
flag, because dao.proposals()
already subscribes to updates for all the cached data.