mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-31 16:07:27 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			118 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			118 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| Dataloader
 | |
| ==========
 | |
| 
 | |
| DataLoader is a generic utility to be used as part of your application's
 | |
| data fetching layer to provide a simplified and consistent API over
 | |
| various remote data sources such as databases or web services via batching
 | |
| and caching. It is provided by a separate package `aiodataloader <https://pypi.org/project/aiodataloader/>`.
 | |
| 
 | |
| 
 | |
| Batching
 | |
| --------
 | |
| 
 | |
| Batching is not an advanced feature, it's DataLoader's primary feature.
 | |
| Create loaders by providing a batch loading function.
 | |
| 
 | |
| .. code:: python
 | |
| 
 | |
|     from aiodataloader import DataLoader
 | |
| 
 | |
|     class UserLoader(DataLoader):
 | |
|         async def batch_load_fn(self, keys):
 | |
|             # Here we call a function to return a user for each key in keys
 | |
|             return [get_user(id=key) for key in keys]
 | |
| 
 | |
| 
 | |
| A batch loading async function accepts a list of keys, and returns a list of ``values``.
 | |
| 
 | |
| 
 | |
| ``DataLoader`` will coalesce all individual loads which occur within a
 | |
| single frame of execution (executed once the wrapping event loop is resolved)
 | |
| and then call your batch function with all requested keys.
 | |
| 
 | |
| 
 | |
| .. code:: python
 | |
| 
 | |
|     user_loader = UserLoader()
 | |
| 
 | |
|     user1 = await user_loader.load(1)
 | |
|     user1_best_friend = await user_loader.load(user1.best_friend_id)
 | |
| 
 | |
|     user2 = await user_loader.load(2)
 | |
|     user2_best_friend = await user_loader.load(user2.best_friend_id)
 | |
| 
 | |
| 
 | |
| A naive application may have issued *four* round-trips to a backend for the
 | |
| required information, but with ``DataLoader`` this application will make at most *two*.
 | |
| 
 | |
| Note that loaded values are one-to-one with the keys and must have the same
 | |
| order. This means that if you load all values from a single query, you must
 | |
| make sure that you then order the query result for the results to match the keys:
 | |
| 
 | |
| 
 | |
| .. code:: python
 | |
| 
 | |
|    class UserLoader(DataLoader):
 | |
|        async def batch_load_fn(self, keys):
 | |
|            users = {user.id: user for user in User.objects.filter(id__in=keys)}
 | |
|            return [users.get(user_id) for user_id in keys]
 | |
| 
 | |
| 
 | |
| ``DataLoader`` allows you to decouple unrelated parts of your application without
 | |
| sacrificing the performance of batch data-loading. While the loader presents
 | |
| an API that loads individual values, all concurrent requests will be coalesced
 | |
| and presented to your batch loading function. This allows your application to
 | |
| safely distribute data fetching requirements throughout your application and
 | |
| maintain minimal outgoing data requests.
 | |
| 
 | |
| 
 | |
| 
 | |
| Using with Graphene
 | |
| -------------------
 | |
| 
 | |
| DataLoader pairs nicely well with Graphene/GraphQL. GraphQL fields are designed
 | |
| to be stand-alone functions. Without a caching or batching mechanism, it's easy
 | |
| for a naive GraphQL server to issue new database requests each time a field is resolved.
 | |
| 
 | |
| Consider the following GraphQL request:
 | |
| 
 | |
| 
 | |
| .. code::
 | |
| 
 | |
|     {
 | |
|       me {
 | |
|         name
 | |
|         bestFriend {
 | |
|           name
 | |
|         }
 | |
|         friends(first: 5) {
 | |
|           name
 | |
|           bestFriend {
 | |
|             name
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
| 
 | |
| If ``me``, ``bestFriend`` and ``friends`` each need to send a request to the backend,
 | |
| there could be at most 13 database requests!
 | |
| 
 | |
| 
 | |
| When using DataLoader, we could define the User type using our previous example with
 | |
| leaner code and at most 4 database requests, and possibly fewer if there are cache hits.
 | |
| 
 | |
| 
 | |
| .. code:: python
 | |
| 
 | |
|     class User(graphene.ObjectType):
 | |
|         name = graphene.String()
 | |
|         best_friend = graphene.Field(lambda: User)
 | |
|         friends = graphene.List(lambda: User)
 | |
| 
 | |
|         async def resolve_best_friend(root, info):
 | |
|             return await user_loader.load(root.best_friend_id)
 | |
| 
 | |
|         async def resolve_friends(root, info):
 | |
|             return await user_loader.load_many(root.friend_ids)
 |