Some clients may send several queries to the server at once (for example, Apollo Client’s query batching). You can execute them concurrently with Schema#multiplex
.
Multiplex runs have their own context, analyzers and instrumentation.
NOTE: As an implementation detail, all queries are run inside multiplexes. That is, a stand-alone query is executed as a “multiplex of one”, so instrumentation and multiplex analyzers and instrumentation will apply to standalone queries run with MySchema.execute(...)
.
To run queries concurrently, build an array of query options, using query:
for the query string. For example:
# Prepare the context for each query:
context = {
current_user: current_user,
}
# Prepare the query options:
queries = [
{
query: "query Query1 { someField }",
variables: {},
operation_name: 'Query1',
context: context,
},
{
query: "query Query2 ($num: Int){ plusOne(num: $num) }",
variables: { num: 3 },
operation_name: 'Query2',
context: context,
}
]
Then, pass them to Schema#multiplex
:
results = MySchema.multiplex(queries)
results
will contain the result for each query in queries
.
Apollo sends the batch variables in a _json
param, you also need to ensure that your schema can handle both batched and non-batched queries, below is an example of the default GraphqlController rewritten to handle Apollo batches:
def execute
context = {}
# Apollo sends the params in a _json variable when batching is enabled
# see the Apollo Documentation about query batching: https://www.apollographql.com/docs/react/advanced/network-layer.html#query-batching
result = if params[:_json]
queries = params[:_json].map do |param|
{
query: param[:query],
operation_name: param[:operationName],
variables: ensure_hash(param[:variables]),
context: context
}
end
MySchema.multiplex(queries)
else
MySchema.execute(
params[:query],
operation_name: params[:operationName],
variables: ensure_hash(params[:variables]),
context: context
)
end
render json: result
end
If Apollo Client has issues recognizing the result of render json: result
, replace it with render body: result.to_json, content_type: 'application/json'
.
Each query is validated and analyzed independently. The results
array may include a mix of successful results and failed results
You can add values to Execution::Multiplex#context
by providing a context:
hash:
MySchema.multiplex(queries, context: { current_user: current_user })
This will be available to instrumentation as multiplex.context[:current_user]
(see below).
You can analyze all queries in a multiplex by adding a multiplex analyzer. For example:
class MySchema < GraphQL::Schema
# ...
multiplex_analyzer(MyAnalyzer)
end
The API is the same as query analyzers, with some considerations:
initial_value
is called at the start of the multiplex (not query)final
is called at the end of the multiplex (not query)call(...)
is called for each node in each query, so it will visit every node in the multiplex in sequence.Multiplex analyzers may return AnalysisError
to halt execution of the whole multiplex.
You can add hooks for each multiplex run with multiplex instrumentation.
An instrumenter must implement .before_multiplex(multiplex)
and .after_multiplex(multiplex)
. Then, it can be mounted with instrument(:multiplex, MyMultiplexAnalyzer)
. See Execution::Multiplex
for available methods.
For example:
# Count how many queries are in the multiplex run:
module MultiplexCounter
def self.before_multiplex(multiplex)
Rails.logger.info("Multiplex size: #{multiplex.queries.length}")
end
def self.after_multiplex(multiplex)
end
end
# ...
class MySchema < GraphQL::Schema
# ...
instrument(:multiplex, MultiplexCounter)
end
Now, MultiplexCounter.before_multiplex
will be called before each multiplex and .after_multiplex
will run after each multiplex.