Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Book #1230

Merged
merged 97 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
bbf5753
Bootstrap
tyranron Dec 1, 2023
a7eeaaf
Rework introduction
tyranron Dec 1, 2023
4a76162
Rework quickstart [skip ci]
tyranron Dec 1, 2023
b708b23
Reorganize "Type System" section [skip ci]
tyranron Dec 4, 2023
818072b
Rework "Scalars" chapter
tyranron Dec 5, 2023
2a7aecd
Move "Scalars" chapter down
tyranron Dec 5, 2023
a6a0aa6
Rework "Objects" chapter [skip ci]
tyranron Dec 5, 2023
352f17b
Rework "Enums" chapter [skip ci]
tyranron Dec 5, 2023
4a9578f
Apply typo
tyranron Dec 6, 2023
2cf2f03
Fix typo
tyranron Dec 6, 2023
d842576
Rephrase
tyranron Dec 6, 2023
9435c62
Rephrase
tyranron Dec 6, 2023
0ba9b69
Fix typo
tyranron Dec 6, 2023
8f51da3
Rephrase
tyranron Dec 6, 2023
23e2a58
Rephrase
tyranron Dec 6, 2023
dd1eb64
Fix typo
tyranron Dec 6, 2023
f749957
Rephrase [skip ci]
tyranron Dec 6, 2023
fda0ba7
Rephrase [skip ci]
tyranron Dec 7, 2023
14fb50f
Mention `Nullable` [skip ci]
tyranron Dec 7, 2023
5d410eb
Rework "Complex fields" chapter and rename "Using contexts" chapter a…
tyranron Dec 7, 2023
b598ad5
Rework "Input objects" chapter [skip ci]
tyranron Dec 7, 2023
c3c453c
Rework "Context" chapter [skip ci]
tyranron Dec 11, 2023
ba17509
Fix code examples [skip ci]
tyranron Dec 11, 2023
16acc8e
Merge branch 'master' into book-rework
tyranron Dec 12, 2023
753806b
Fix code examples [skip ci]
tyranron Dec 12, 2023
1aa970f
Rework "Interfaces" chapter, vol.1
tyranron Dec 12, 2023
511ea81
Rework "Interfaces" chapter, vol.2
tyranron Dec 13, 2023
46f46c3
Fix integration tests
tyranron Dec 13, 2023
ac3e28a
Rework "Unions" chapter
tyranron Dec 13, 2023
c2e4a09
Fix codegen tests
tyranron Dec 13, 2023
6abddc6
Rework "Introspection" chapter
tyranron Dec 14, 2023
0f18c88
Rework "Implicit and explicit `null`" chapter
tyranron Dec 14, 2023
0516ede
Fix
tyranron Dec 14, 2023
322c0a3
Move "Objects and generics" chapter under "Objects" section
tyranron Dec 15, 2023
fed92f0
Merge "Non-struct objects" chapter into "Objects > Error handling"
tyranron Dec 15, 2023
dedabe2
Move "Subscriptions" chapter under "Servers" section
tyranron Dec 15, 2023
41b5288
Rework "Servers" chapters
tyranron Dec 15, 2023
1cfbc09
Move "Subscription" chapter under "Schema" section
tyranron Dec 15, 2023
b9c2e78
Mention WebSocket integration in "Serving" chapter
tyranron Dec 15, 2023
698ae08
Rework "Multiple operations per request" chapter into "Batching"
tyranron Dec 21, 2023
6cf32f5
Rework "Error handling" chapter, vol.1
tyranron Dec 22, 2023
2090f4f
Update book/src/types/objects/error/field.md
tyranron Dec 25, 2023
6d64778
Update book/src/types/objects/error/field.md
tyranron Dec 25, 2023
c26c93c
Update book/src/types/objects/error/field.md
tyranron Dec 25, 2023
b16f506
Update book/src/serve/index.md
tyranron Dec 25, 2023
63c3bfd
Update book/src/serve/batching.md
tyranron Dec 25, 2023
a356c14
Update book/src/serve/batching.md
tyranron Dec 25, 2023
c8508f3
Update book/src/types/objects/context.md
tyranron Dec 25, 2023
315a522
Update book/src/types/enums.md
tyranron Dec 25, 2023
8b376f3
Update book/src/types/objects/complex_fields.md
tyranron Dec 25, 2023
f514397
Update book/src/types/objects/complex_fields.md [skip ci]
tyranron Dec 25, 2023
5856cc2
Update book/src/types/enums.md [skip ci]
tyranron Dec 25, 2023
8155c74
Fix [skip ci]
tyranron Dec 25, 2023
23046b9
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
f31049d
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
5434a0f
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
97c1f94
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
4fbf255
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
65536f6
Update book/src/types/enums.md [skip ci]
tyranron Dec 25, 2023
c0e9ed1
Update book/src/types/input_objects.md [skip ci]
tyranron Dec 25, 2023
536876e
Update book/src/types/input_objects.md [skip ci]
tyranron Dec 25, 2023
449886e
Update book/src/types/input_objects.md [skip ci]
tyranron Dec 25, 2023
5aacb5c
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
fac351f
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
58619fc
Update book/src/types/objects/complex_fields.md [skip ci]
tyranron Dec 25, 2023
578b308
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
6ad43b6
Fix https://github.com/graphql-rust/juniper/pull/1230#discussion_r143…
tyranron Dec 25, 2023
073016f
Rework "Error handling" chapter, vol.2
tyranron Dec 26, 2023
cc8d9bb
Rework "Type system" chapter
tyranron Dec 26, 2023
b7667fa
Merge branch 'master' into book-rework
tyranron Jan 5, 2024
a673c6a
Rework "Generics" chapter
tyranron Jan 5, 2024
f9bebdd
Rework "Schemas" chapter
tyranron Jan 5, 2024
1aeb9da
Disable `schema-language` feature by default
tyranron Jan 5, 2024
4770e31
Merge branch 'master' into book-rework
tyranron Jan 9, 2024
b0f0e95
Rework "Subscriptions" chapter
tyranron Jan 9, 2024
165079f
Reorganize "Dataloaders" chapter
tyranron Jan 9, 2024
d46d0bd
Describe "N+1 problem" chapter
tyranron Jan 9, 2024
b5623a6
Fix codegen failure tests
tyranron Jan 9, 2024
a2a7e41
Rework "Dataloaders" chapter
tyranron Jan 10, 2024
581c2d0
Add "Eager loading" chapter
tyranron Jan 11, 2024
b44d5b6
Merge branch 'master' into book-rework
tyranron Jan 11, 2024
558a0a7
Merge branch 'master' into book-rework
tyranron Jan 15, 2024
dbb838d
Merge branch 'master' into book-rework
tyranron Jan 19, 2024
8e406e6
Bootstrap "Look-ahead" chapter
tyranron Jan 19, 2024
b71677d
Merge branch 'master' into book-rework
tyranron Feb 2, 2024
b3119b8
Merge branch 'master' into book-rework
tyranron Feb 27, 2024
b6fb4fe
Describing look-ahead, vol.2
tyranron Mar 12, 2024
8ea9878
Merge branch 'master' into book-rework
tyranron Mar 12, 2024
a09a3e8
Fix N+1 look-ahead example
tyranron Mar 12, 2024
af3d892
Finish "Look-ahead" chapter
tyranron Mar 12, 2024
b5c3f87
Upd "Eager loading" chapter
tyranron Mar 12, 2024
ea3b7d3
Fix codegen failure tests
tyranron Mar 15, 2024
58e12b8
Fix codegen failure tests
tyranron Mar 15, 2024
e46dccb
Fix releasing `juniper` crate
tyranron Mar 15, 2024
a4aedaa
Fix releasing `juniper_subscriptions` crate
tyranron Mar 15, 2024
e50cb18
Fix links leading to Book
tyranron Mar 20, 2024
acdf3f4
Fix Book releasing
tyranron Mar 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ jobs:
deploy-book:
name: deploy (Book)
if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper@') }}
|| startsWith(github.ref, 'refs/tags/juniper') }}
needs: ["codespell", "test", "test-book"]
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
[rocket_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket/examples
[hyper]: https://hyper.rs
[rocket]: https://rocket.rs
[book]: https://graphql-rust.github.io
[book]: https://graphql-rust.github.io/juniper
[book_master]: https://graphql-rust.github.io/juniper/master
[book_index]: https://graphql-rust.github.io
[book_quickstart]: https://graphql-rust.github.io/quickstart.html
[book_index]: https://graphql-rust.github.io/juniper
[book_quickstart]: https://graphql-rust.github.io/juniper/quickstart.html
[docsrs]: https://docs.rs/juniper
[warp]: https://github.com/seanmonstar/warp
[warp_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_warp/examples
Expand Down
1 change: 1 addition & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ authors = ["Christoph Herzog <[email protected]>"]
publish = false

[dependencies]
dataloader = "0.17" # for Book only
futures = "0.3"
juniper = { path = "../juniper" }

Expand Down
7 changes: 5 additions & 2 deletions book/book.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
[book]
title = "Juniper Book (GraphQL server for Rust)"
title = "Juniper Book"
description = "User guide for Juniper (GraphQL server library for Rust)."
language = "en"
multilingual = false
authors = [
"Kai Ren (@tyranron)",
]
src = "src"

[build]
build-dir = "_rendered"
create-missing = false

[output.html]
git_repository_url = "https://github.com/graphql-rs/juniper"
git_repository_url = "https://github.com/graphql-rust/juniper"

[rust]
edition = "2021"
73 changes: 0 additions & 73 deletions book/src/README.md

This file was deleted.

57 changes: 23 additions & 34 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
- [Introduction](README.md)
- [Quickstart](quickstart.md)

- [Type System](types/index.md)
# Summary

- [Defining objects](types/objects/defining_objects.md)
- [Complex fields](types/objects/complex_fields.md)
- [Using contexts](types/objects/using_contexts.md)
- [Error handling](types/objects/error_handling.md)
- [Other types](types/other-index.md)
- [Enums](types/enums.md)
- [Introduction](introduction.md)
- [Quickstart](quickstart.md)
- [Type system](types/index.md)
- [Objects](types/objects/index.md)
- [Complex fields](types/objects/complex_fields.md)
- [Context](types/objects/context.md)
- [Error handling](types/objects/error/index.md)
- [Field errors](types/objects/error/field.md)
- [Schema errors](types/objects/error/schema.md)
- [Generics](types/objects/generics.md)
- [Interfaces](types/interfaces.md)
- [Unions](types/unions.md)
- [Enums](types/enums.md)
- [Input objects](types/input_objects.md)
- [Scalars](types/scalars.md)
- [Unions](types/unions.md)

- [Schemas and mutations](schema/schemas_and_mutations.md)

- [Adding A Server](servers/index.md)

- [Official Server Integrations](servers/official.md) - [Hyper](servers/hyper.md)
- [Warp](servers/warp.md)
- [Rocket](servers/rocket.md)
- [Hyper](servers/hyper.md)
- [Third Party Integrations](servers/third-party.md)

- [Schema](schema/index.md)
- [Subscriptions](schema/subscriptions.md)
- [Introspection](schema/introspection.md)
- [Serving](serve/index.md)
- [Batching](serve/batching.md)
- [Advanced Topics](advanced/index.md)

- [Introspection](advanced/introspection.md)
- [Non-struct objects](advanced/non_struct_objects.md)
- [Implicit and explicit null](advanced/implicit_and_explicit_null.md)
- [Objects and generics](advanced/objects_and_generics.md)
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
- [Dataloaders](advanced/dataloaders.md)
- [Subscriptions](advanced/subscriptions.md)

# - [Context switching]

# - [Dynamic type system]
- [Implicit and explicit `null`](advanced/implicit_and_explicit_null.md)
- [N+1 problem](advanced/n_plus_1.md)
- [DataLoader](advanced/dataloader.md)
- [Look-ahead](advanced/lookahead.md)
- [Eager loading](advanced/eager_loading.md)
198 changes: 198 additions & 0 deletions book/src/advanced/dataloader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
DataLoader
==========

DataLoader pattern, named after the correspondent [`dataloader` NPM package][0], represents a mechanism of batching and caching data requests in a delayed manner for solving the [N+1 problem](n_plus_1.md).

> A port of the "Loader" API originally developed by [@schrockn] at Facebook in 2010 as a simplifying force to coalesce the sundry key-value store back-end APIs which existed at the time. At Facebook, "Loader" became one of the implementation details of the "Ent" framework, a privacy-aware data entity loading and caching layer within web server product code. This ultimately became the underpinning for Facebook's GraphQL server implementation and type definitions.

In [Rust] ecosystem, DataLoader pattern is introduced with the [`dataloader` crate][1], naturally usable with [Juniper].

Let's remake our [example of N+1 problem](n_plus_1.md), so it's solved by applying the DataLoader pattern:
```rust
# extern crate anyhow;
# extern crate dataloader;
# extern crate juniper;
# use std::{collections::HashMap, sync::Arc};
# use anyhow::anyhow;
# use dataloader::non_cached::Loader;
# use juniper::{graphql_object, GraphQLObject};
#
# type CultId = i32;
# type UserId = i32;
#
# struct Repository;
#
# impl Repository {
# async fn load_cults_by_ids(&self, cult_ids: &[CultId]) -> anyhow::Result<HashMap<CultId, Cult>> { unimplemented!() }
# async fn load_all_persons(&self) -> anyhow::Result<Vec<Person>> { unimplemented!() }
# }
#
struct Context {
repo: Repository,
cult_loader: CultLoader,
}

impl juniper::Context for Context {}

#[derive(Clone, GraphQLObject)]
struct Cult {
id: CultId,
name: String,
}

struct CultBatcher {
repo: Repository,
}

// Since `BatchFn` doesn't provide any notion of fallible loading, like
// `try_load()` returning `Result<HashMap<K, V>, E>`, we handle possible
// errors as loaded values and unpack them later in the resolver.
impl dataloader::BatchFn<CultId, Result<Cult, Arc<anyhow::Error>>> for CultBatcher {
async fn load(
&mut self,
cult_ids: &[CultId],
) -> HashMap<CultId, Result<Cult, Arc<anyhow::Error>>> {
// Effectively performs the following SQL query:
// SELECT id, name FROM cults WHERE id IN (${cult_id1}, ${cult_id2}, ...)
match self.repo.load_cults_by_ids(cult_ids).await {
Ok(found_cults) => {
found_cults.into_iter().map(|(id, cult)| (id, Ok(cult))).collect()
}
// One could choose a different strategy to deal with fallible loads,
// like consider values that failed to load as absent, or just panic.
// See cksac/dataloader-rs#35 for details:
// https://github.com/cksac/dataloader-rs/issues/35
Err(e) => {
// Since `anyhow::Error` doesn't implement `Clone`, we have to
// work around here.
let e = Arc::new(e);
cult_ids.iter().map(|k| (k.clone(), Err(e.clone()))).collect()
}
}
}
}

type CultLoader = Loader<CultId, Result<Cult, Arc<anyhow::Error>>, CultBatcher>;

fn new_cult_loader(repo: Repository) -> CultLoader {
CultLoader::new(CultBatcher { repo })
// Usually a `Loader` will coalesce all individual loads which occur
// within a single frame of execution before calling a `BatchFn::load()`
// with all the collected keys. However, sometimes this behavior is not
// desirable or optimal (perhaps, a request is expected to be spread out
// over a few subsequent ticks).
// A larger yield count will allow more keys to be appended to the batch,
// but will wait longer before the actual load. For more details see:
// https://github.com/cksac/dataloader-rs/issues/12
// https://github.com/graphql/dataloader#batch-scheduling
.with_yield_count(100)
}

struct Person {
id: UserId,
name: String,
cult_id: CultId,
}

#[graphql_object]
#[graphql(context = Context)]
impl Person {
fn id(&self) -> CultId {
self.id
}

fn name(&self) -> &str {
self.name.as_str()
}

async fn cult(&self, ctx: &Context) -> anyhow::Result<Cult> {
ctx.cult_loader
// Here, we don't run the `CultBatcher::load()` eagerly, but rather
// only register the `self.cult_id` value in the `cult_loader` and
// wait for other concurrent resolvers to do the same.
// The actual batch loading happens once all the resolvers register
// their IDs and there is nothing more to execute.
.try_load(self.cult_id)
.await
// The outer error is the `io::Error` returned by `try_load()` if
// no value is present in the `HashMap` for the specified
// `self.cult_id`, meaning that there is no `Cult` with such ID
// in the `Repository`.
.map_err(|_| anyhow!("No cult exists for ID `{}`", self.cult_id))?
// The inner error is the one returned by the `CultBatcher::load()`
// if the `Repository::load_cults_by_ids()` fails, meaning that
// running the SQL query failed.
.map_err(|arc_err| anyhow!("{arc_err}"))
}
}

struct Query;

#[graphql_object]
#[graphql(context = Context)]
impl Query {
async fn persons(ctx: &Context) -> anyhow::Result<Vec<Person>> {
// Effectively performs the following SQL query:
// SELECT id, name, cult_id FROM persons
ctx.repo.load_all_persons().await
}
}

fn main() {

}
```

And now, performing a [GraphQL query which lead to N+1 problem](n_plus_1.md)
```graphql
query {
persons {
id
name
cult {
id
name
}
}
}
```
will lead to efficient [SQL] queries, just as expected:
```sql
SELECT id, name, cult_id FROM persons;
SELECT id, name FROM cults WHERE id IN (1, 2, 3, 4);
```




## Caching

[`dataloader::cached`] provides a [memoization][2] cache: after `BatchFn::load()` is called once with given keys, the resulting values are cached to eliminate redundant loads.

DataLoader caching does not replace [Redis], [Memcached], 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][3].

> **WARNING**: A DataLoader should be created per-request to avoid risk of bugs where one client is able to load cached/batched data from another client outside its authenticated scope. Creating a DataLoader within an individual resolver will prevent batching from occurring and will nullify any benefits of it.




## Full example

For a full example using DataLoaders in [Juniper] check out the [`jayy-lmao/rust-graphql-docker` repository][4].




[`dataloader::cached`]: https://docs.rs/dataloader/latest/dataloader/cached/index.html
[@schrockn]: https://github.com/schrockn
[Juniper]: https://docs.rs/juniper
[Memcached]: https://memcached.org
[Redis]: https://redis.io
[Rust]: https://www.rust-lang.org
[SQL]: https://en.wikipedia.org/wiki/SQL

[0]: https://github.com/graphql/dataloader
[1]: https://docs.rs/crate/dataloader
[2]: https://en.wikipedia.org/wiki/Memoization
[3]: https://github.com/graphql/dataloader#caching
[4]: https://github.com/jayy-lmao/rust-graphql-docker
Loading
Loading