Updated book for master ***NO_CI***
This commit is contained in:
parent
f8159cb8d2
commit
ce6327d090
4 changed files with 62 additions and 50 deletions
|
@ -165,21 +165,25 @@ SELECT id, name FROM cults WHERE id = 2;
|
|||
<p>Once the list of users has been returned, a separate query is run to find the cult of each user.
|
||||
You can see how this could quickly become a problem.</p>
|
||||
<p>A common solution to this is to introduce a <strong>dataloader</strong>.
|
||||
This can be done with Juniper using the crate <a href="https://github.com/cksac/dataloader-rs">cksac/dataloader-rs</a>, which has two types of dataloaders; cached and non-cached. This example will explore the non-cached option.</p>
|
||||
This can be done with Juniper using the crate <a href="https://github.com/cksac/dataloader-rs">cksac/dataloader-rs</a>, which has two types of dataloaders; cached and non-cached.</p>
|
||||
<a class="header" href="#cached-loader" id="cached-loader"><h4>Cached Loader</h4></a>
|
||||
<p>DataLoader provides a memoization cache, after .load() is called once with a given key, the resulting value is cached to eliminate redundant loads.</p>
|
||||
<p>DataLoader caching does not replace Redis, Memcache, or any other shared application-level cache. DataLoader is first and foremost a data loading mechanism, and its cache only serves the purpose of not repeatedly loading the same data in the context of a single request to your Application. <a href="https://github.com/graphql/dataloader#caching">(read more)</a></p>
|
||||
<a class="header" href="#what-does-it-look-like" id="what-does-it-look-like"><h3>What does it look like?</h3></a>
|
||||
<p>!FILENAME Cargo.toml</p>
|
||||
<pre><code class="language-toml">[dependencies]
|
||||
actix-identity = "0.2"
|
||||
actix-rt = "1.0"
|
||||
actix-web = {version = "2.0", features = []}
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] }
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper" }
|
||||
futures = "0.3"
|
||||
postgres = "0.15.2"
|
||||
dataloader = "0.6.0"
|
||||
dataloader = "0.12.0"
|
||||
async-trait = "0.1.30"
|
||||
</code></pre>
|
||||
<pre><code class="language-rust ignore">use dataloader::Loader;
|
||||
use dataloader::{BatchFn, BatchFuture};
|
||||
use futures::{future, FutureExt as _};
|
||||
<pre><code class="language-rust ignore">// use dataloader::cached::Loader;
|
||||
use dataloader::non_cached::Loader;
|
||||
use dataloader::BatchFn;
|
||||
use std::collections::HashMap;
|
||||
use postgres::{Connection, TlsMode};
|
||||
use std::env;
|
||||
|
@ -214,26 +218,31 @@ pub fn get_cult_by_ids(hashmap: &mut HashMap<i32, Cult>, ids: Vec<i
|
|||
|
||||
pub struct CultBatcher;
|
||||
|
||||
#[async_trait]
|
||||
impl BatchFn<i32, Cult> for CultBatcher {
|
||||
type Error = ();
|
||||
|
||||
fn load(&self, keys: &[i32]) -> BatchFuture<Cult, Self::Error> {
|
||||
println!("load batch {:?}", keys);
|
||||
// A hashmap is used, as we need to return an array which maps each original key to a Cult.
|
||||
let mut cult_hashmap = HashMap::new();
|
||||
get_cult_by_ids(&mut cult_hashmap, keys.to_vec());
|
||||
|
||||
future::ready(keys.iter().map(|key| cult_hashmap[key].clone()).collect())
|
||||
.unit_error()
|
||||
.boxed()
|
||||
}
|
||||
async fn load(&self, keys: &[i32]) -> HashMap<i32, Cult> {
|
||||
println!("load cult batch {:?}", keys);
|
||||
let mut cult_hashmap = HashMap::new();
|
||||
get_cult_by_ids(&mut cult_hashmap, keys.to_vec());
|
||||
cult_hashmap
|
||||
}
|
||||
}
|
||||
|
||||
pub type CultLoader = Loader<i32, Cult, (), CultBatcher>;
|
||||
pub type CultLoader = Loader<i32, Cult, CultBatcher>;
|
||||
|
||||
// To create a new loader
|
||||
pub fn get_loader() -> CultLoader {
|
||||
Loader::new(CultBatcher)
|
||||
// Usually a DataLoader will coalesce all individual loads which occur
|
||||
// within a single frame of execution before calling your batch function with all requested keys.
|
||||
// However sometimes this behavior is not desirable or optimal.
|
||||
// Perhaps you expect requests to be spread out over a few subsequent ticks
|
||||
// See: https://github.com/cksac/dataloader-rs/issues/12
|
||||
// More info: https://github.com/graphql/dataloader#batch-scheduling
|
||||
// A larger yield count will allow more requests to append to batch but will wait longer before actual load.
|
||||
.with_yield_count(100)
|
||||
}
|
||||
|
||||
#[juniper::graphql_object(Context = Context)]
|
||||
|
@ -242,15 +251,14 @@ impl Cult {
|
|||
|
||||
// To call the dataloader
|
||||
pub async fn cult_by_id(ctx: &Context, id: i32) -> Cult {
|
||||
ctx.cult_loader.load(id).await.unwrap()
|
||||
ctx.cult_loader.load(id).await
|
||||
}
|
||||
}
|
||||
|
||||
</code></pre>
|
||||
<a class="header" href="#how-do-i-call-them" id="how-do-i-call-them"><h3>How do I call them?</h3></a>
|
||||
<p>Once created, a dataloader has the functions <code>.load()</code> and <code>.load_many()</code>.
|
||||
When called these return a Future.
|
||||
In the above example <code>cult_loader.load(id: i32)</code> returns <code>Future<Cult></code>. If we had used <code>cult_loader.load_many(Vec<i32>)</code> it would have returned <code>Future<Vec<Cult>></code>.</p>
|
||||
<p>Once created, a dataloader has the async functions <code>.load()</code> and <code>.load_many()</code>.
|
||||
In the above example <code>cult_loader.load(id: i32).await</code> returns <code>Cult</code>. If we had used <code>cult_loader.load_many(Vec<i32>).await</code> it would have returned <code>Vec<Cult></code>.</p>
|
||||
<a class="header" href="#where-do-i-create-my-dataloaders" id="where-do-i-create-my-dataloaders"><h3>Where do I create my dataloaders?</h3></a>
|
||||
<p><strong>Dataloaders</strong> should be created per-request to avoid risk of bugs where one user is able to load cached/batched data from another user/ outside of its authenticated scope.
|
||||
Creating dataloaders within individual resolvers will prevent batching from occurring and will nullify the benefits of the dataloader.</p>
|
||||
|
@ -278,15 +286,13 @@ impl Context {
|
|||
st: web::Data<Arc<Schema>>,
|
||||
data: web::Json<GraphQLRequest>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let mut rt = futures::executor::LocalPool::new();
|
||||
|
||||
// Context setup
|
||||
let cult_loader = get_loader();
|
||||
let ctx = Context::new(cult_loader);
|
||||
|
||||
// Execute
|
||||
let future_execute = data.execute(&st, &ctx);
|
||||
let res = rt.run_until(future_execute);
|
||||
let res = data.execute(&st, &ctx).await;
|
||||
let json = serde_json::to_string(&res).map_err(error::ErrorInternalServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
|
|
|
@ -2281,21 +2281,25 @@ SELECT id, name FROM cults WHERE id = 2;
|
|||
<p>Once the list of users has been returned, a separate query is run to find the cult of each user.
|
||||
You can see how this could quickly become a problem.</p>
|
||||
<p>A common solution to this is to introduce a <strong>dataloader</strong>.
|
||||
This can be done with Juniper using the crate <a href="https://github.com/cksac/dataloader-rs">cksac/dataloader-rs</a>, which has two types of dataloaders; cached and non-cached. This example will explore the non-cached option.</p>
|
||||
This can be done with Juniper using the crate <a href="https://github.com/cksac/dataloader-rs">cksac/dataloader-rs</a>, which has two types of dataloaders; cached and non-cached.</p>
|
||||
<a class="header" href="#cached-loader" id="cached-loader"><h4>Cached Loader</h4></a>
|
||||
<p>DataLoader provides a memoization cache, after .load() is called once with a given key, the resulting value is cached to eliminate redundant loads.</p>
|
||||
<p>DataLoader caching does not replace Redis, Memcache, or any other shared application-level cache. DataLoader is first and foremost a data loading mechanism, and its cache only serves the purpose of not repeatedly loading the same data in the context of a single request to your Application. <a href="https://github.com/graphql/dataloader#caching">(read more)</a></p>
|
||||
<a class="header" href="#what-does-it-look-like" id="what-does-it-look-like"><h3>What does it look like?</h3></a>
|
||||
<p>!FILENAME Cargo.toml</p>
|
||||
<pre><code class="language-toml">[dependencies]
|
||||
actix-identity = "0.2"
|
||||
actix-rt = "1.0"
|
||||
actix-web = {version = "2.0", features = []}
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] }
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper" }
|
||||
futures = "0.3"
|
||||
postgres = "0.15.2"
|
||||
dataloader = "0.6.0"
|
||||
dataloader = "0.12.0"
|
||||
async-trait = "0.1.30"
|
||||
</code></pre>
|
||||
<pre><code class="language-rust ignore">use dataloader::Loader;
|
||||
use dataloader::{BatchFn, BatchFuture};
|
||||
use futures::{future, FutureExt as _};
|
||||
<pre><code class="language-rust ignore">// use dataloader::cached::Loader;
|
||||
use dataloader::non_cached::Loader;
|
||||
use dataloader::BatchFn;
|
||||
use std::collections::HashMap;
|
||||
use postgres::{Connection, TlsMode};
|
||||
use std::env;
|
||||
|
@ -2330,26 +2334,31 @@ pub fn get_cult_by_ids(hashmap: &mut HashMap<i32, Cult>, ids: Vec<i
|
|||
|
||||
pub struct CultBatcher;
|
||||
|
||||
#[async_trait]
|
||||
impl BatchFn<i32, Cult> for CultBatcher {
|
||||
type Error = ();
|
||||
|
||||
fn load(&self, keys: &[i32]) -> BatchFuture<Cult, Self::Error> {
|
||||
println!("load batch {:?}", keys);
|
||||
// A hashmap is used, as we need to return an array which maps each original key to a Cult.
|
||||
let mut cult_hashmap = HashMap::new();
|
||||
get_cult_by_ids(&mut cult_hashmap, keys.to_vec());
|
||||
|
||||
future::ready(keys.iter().map(|key| cult_hashmap[key].clone()).collect())
|
||||
.unit_error()
|
||||
.boxed()
|
||||
}
|
||||
async fn load(&self, keys: &[i32]) -> HashMap<i32, Cult> {
|
||||
println!("load cult batch {:?}", keys);
|
||||
let mut cult_hashmap = HashMap::new();
|
||||
get_cult_by_ids(&mut cult_hashmap, keys.to_vec());
|
||||
cult_hashmap
|
||||
}
|
||||
}
|
||||
|
||||
pub type CultLoader = Loader<i32, Cult, (), CultBatcher>;
|
||||
pub type CultLoader = Loader<i32, Cult, CultBatcher>;
|
||||
|
||||
// To create a new loader
|
||||
pub fn get_loader() -> CultLoader {
|
||||
Loader::new(CultBatcher)
|
||||
// Usually a DataLoader will coalesce all individual loads which occur
|
||||
// within a single frame of execution before calling your batch function with all requested keys.
|
||||
// However sometimes this behavior is not desirable or optimal.
|
||||
// Perhaps you expect requests to be spread out over a few subsequent ticks
|
||||
// See: https://github.com/cksac/dataloader-rs/issues/12
|
||||
// More info: https://github.com/graphql/dataloader#batch-scheduling
|
||||
// A larger yield count will allow more requests to append to batch but will wait longer before actual load.
|
||||
.with_yield_count(100)
|
||||
}
|
||||
|
||||
#[juniper::graphql_object(Context = Context)]
|
||||
|
@ -2358,15 +2367,14 @@ impl Cult {
|
|||
|
||||
// To call the dataloader
|
||||
pub async fn cult_by_id(ctx: &Context, id: i32) -> Cult {
|
||||
ctx.cult_loader.load(id).await.unwrap()
|
||||
ctx.cult_loader.load(id).await
|
||||
}
|
||||
}
|
||||
|
||||
</code></pre>
|
||||
<a class="header" href="#how-do-i-call-them" id="how-do-i-call-them"><h3>How do I call them?</h3></a>
|
||||
<p>Once created, a dataloader has the functions <code>.load()</code> and <code>.load_many()</code>.
|
||||
When called these return a Future.
|
||||
In the above example <code>cult_loader.load(id: i32)</code> returns <code>Future<Cult></code>. If we had used <code>cult_loader.load_many(Vec<i32>)</code> it would have returned <code>Future<Vec<Cult>></code>.</p>
|
||||
<p>Once created, a dataloader has the async functions <code>.load()</code> and <code>.load_many()</code>.
|
||||
In the above example <code>cult_loader.load(id: i32).await</code> returns <code>Cult</code>. If we had used <code>cult_loader.load_many(Vec<i32>).await</code> it would have returned <code>Vec<Cult></code>.</p>
|
||||
<a class="header" href="#where-do-i-create-my-dataloaders" id="where-do-i-create-my-dataloaders"><h3>Where do I create my dataloaders?</h3></a>
|
||||
<p><strong>Dataloaders</strong> should be created per-request to avoid risk of bugs where one user is able to load cached/batched data from another user/ outside of its authenticated scope.
|
||||
Creating dataloaders within individual resolvers will prevent batching from occurring and will nullify the benefits of the dataloader.</p>
|
||||
|
@ -2394,15 +2402,13 @@ impl Context {
|
|||
st: web::Data<Arc<Schema>>,
|
||||
data: web::Json<GraphQLRequest>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let mut rt = futures::executor::LocalPool::new();
|
||||
|
||||
// Context setup
|
||||
let cult_loader = get_loader();
|
||||
let ctx = Context::new(cult_loader);
|
||||
|
||||
// Execute
|
||||
let future_execute = data.execute(&st, &ctx);
|
||||
let res = rt.run_until(future_execute);
|
||||
let res = data.execute(&st, &ctx).await;
|
||||
let json = serde_json::to_string(&res).map_err(error::ErrorInternalServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue