Skip to content

Commit 91a3353

Browse files
authored
Update dataloader explanation code (#661)
1 parent aedb2d1 commit 91a3353

File tree

1 file changed

+31
-24
lines changed

1 file changed

+31
-24
lines changed

docs/book/content/advanced/dataloaders.md

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ Once the list of users has been returned, a separate query is run to find the cu
3535
You can see how this could quickly become a problem.
3636

3737
A common solution to this is to introduce a **dataloader**.
38-
This can be done with Juniper using the crate [cksac/dataloader-rs](https://github.com/cksac/dataloader-rs), which has two types of dataloaders; cached and non-cached. This example will explore the non-cached option.
38+
This can be done with Juniper using the crate [cksac/dataloader-rs](https://github.com/cksac/dataloader-rs), which has two types of dataloaders; cached and non-cached.
3939

40+
#### Cached Loader
41+
DataLoader provides a memoization cache, after .load() is called once with a given key, the resulting value is cached to eliminate redundant loads.
42+
43+
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. [(read more)](https://github.com/graphql/dataloader#caching)
4044

4145
### What does it look like?
4246

@@ -47,16 +51,17 @@ This can be done with Juniper using the crate [cksac/dataloader-rs](https://gith
4751
actix-identity = "0.2"
4852
actix-rt = "1.0"
4953
actix-web = {version = "2.0", features = []}
50-
juniper = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] }
54+
juniper = { git = "https://github.com/graphql-rust/juniper" }
5155
futures = "0.3"
5256
postgres = "0.15.2"
53-
dataloader = "0.6.0"
57+
dataloader = "0.12.0"
58+
async-trait = "0.1.30"
5459
```
5560

5661
```rust, ignore
57-
use dataloader::Loader;
58-
use dataloader::{BatchFn, BatchFuture};
59-
use futures::{future, FutureExt as _};
62+
// use dataloader::cached::Loader;
63+
use dataloader::non_cached::Loader;
64+
use dataloader::BatchFn;
6065
use std::collections::HashMap;
6166
use postgres::{Connection, TlsMode};
6267
use std::env;
@@ -91,26 +96,31 @@ pub fn get_cult_by_ids(hashmap: &mut HashMap<i32, Cult>, ids: Vec<i32>) {
9196
9297
pub struct CultBatcher;
9398
99+
#[async_trait]
94100
impl BatchFn<i32, Cult> for CultBatcher {
95-
type Error = ();
96101
97-
fn load(&self, keys: &[i32]) -> BatchFuture<Cult, Self::Error> {
98-
println!("load batch {:?}", keys);
99102
// A hashmap is used, as we need to return an array which maps each original key to a Cult.
100-
let mut cult_hashmap = HashMap::new();
101-
get_cult_by_ids(&mut cult_hashmap, keys.to_vec());
102-
103-
future::ready(keys.iter().map(|key| cult_hashmap[key].clone()).collect())
104-
.unit_error()
105-
.boxed()
106-
}
103+
async fn load(&self, keys: &[i32]) -> HashMap<i32, Cult> {
104+
println!("load cult batch {:?}", keys);
105+
let mut cult_hashmap = HashMap::new();
106+
get_cult_by_ids(&mut cult_hashmap, keys.to_vec());
107+
cult_hashmap
108+
}
107109
}
108110
109-
pub type CultLoader = Loader<i32, Cult, (), CultBatcher>;
111+
pub type CultLoader = Loader<i32, Cult, CultBatcher>;
110112
111113
// To create a new loader
112114
pub fn get_loader() -> CultLoader {
113115
Loader::new(CultBatcher)
116+
// Usually a DataLoader will coalesce all individual loads which occur
117+
// within a single frame of execution before calling your batch function with all requested keys.
118+
// However sometimes this behavior is not desirable or optimal.
119+
// Perhaps you expect requests to be spread out over a few subsequent ticks
120+
// See: https://github.com/cksac/dataloader-rs/issues/12
121+
// More info: https://github.com/graphql/dataloader#batch-scheduling
122+
// A larger yield count will allow more requests to append to batch but will wait longer before actual load.
123+
.with_yield_count(100)
114124
}
115125
116126
#[juniper::graphql_object(Context = Context)]
@@ -119,17 +129,16 @@ impl Cult {
119129
120130
// To call the dataloader
121131
pub async fn cult_by_id(ctx: &Context, id: i32) -> Cult {
122-
ctx.cult_loader.load(id).await.unwrap()
132+
ctx.cult_loader.load(id).await
123133
}
124134
}
125135
126136
```
127137

128138
### How do I call them?
129139

130-
Once created, a dataloader has the functions `.load()` and `.load_many()`.
131-
When called these return a Future.
132-
In the above example `cult_loader.load(id: i32)` returns `Future<Cult>`. If we had used `cult_loader.load_many(Vec<i32>)` it would have returned `Future<Vec<Cult>>`.
140+
Once created, a dataloader has the async functions `.load()` and `.load_many()`.
141+
In the above example `cult_loader.load(id: i32).await` returns `Cult`. If we had used `cult_loader.load_many(Vec<i32>).await` it would have returned `Vec<Cult>`.
133142

134143

135144
### Where do I create my dataloaders?
@@ -165,15 +174,13 @@ pub async fn graphql(
165174
st: web::Data<Arc<Schema>>,
166175
data: web::Json<GraphQLRequest>,
167176
) -> Result<HttpResponse, Error> {
168-
let mut rt = futures::executor::LocalPool::new();
169177
170178
// Context setup
171179
let cult_loader = get_loader();
172180
let ctx = Context::new(cult_loader);
173181
174182
// Execute
175-
let future_execute = data.execute(&st, &ctx);
176-
let res = rt.run_until(future_execute);
183+
let res = data.execute(&st, &ctx).await;
177184
let json = serde_json::to_string(&res).map_err(error::ErrorInternalServerError)?;
178185
179186
Ok(HttpResponse::Ok()

0 commit comments

Comments
 (0)