Fork me on GitHub

There's no single answer to that. Essentially, you can imagine that you have a big tree of the actual data, as pulled from an external store or some other service, or calculated on the fly. The schema maps onto that, selecting just parts, but it may all be present. Having extra keys that are not referenced by a particular query is not a problem, nor is having extra keys that are not part of the schema.


But when you start to optimize, there's a few ways. For example, perhaps you have Customer->Order->LineItem->Product and the Product object contains a thumbnail URL. Customer, Order, and LineItem come from one source, Product from the other.


Now, you could always construct Orders containg LineItems, and then query your Product service (or database) and add Product data to each LineItem.


But if a query doesn't reference lineItem.product.thumbnailURL , then that's wasted effort.


Lacinia has a selections API that allows a parent resolver (say, one for Customer or Order) to determine if the active query references a particular field; you could then decide there whether to connect LineItems to Products. We informally call that the Oracular approach because you are peering into the future (of the execution of the query) to see what's needed.


Alternately, you could have a resolver for the LineItem.product field that dynamically reaches out to the Product data source to get Product data for that one line item. That can work, but can lead to the dreaded N+1 queries problem; that is, here you might do one query to get an Order with 5 line items, then do 5 queries to the Product data source, rather than just one (that requests 5 products as a batch).


The N+1 approach is often handled with a data loader, that can aggregate and batch those kinds of queries on-the-fly. There's a related project, whose name escapes me (sorry!) that provides those kinds of features.


Internally, we experimented with an async data loader approach, but it has a lot of challenges; ultimately, when we started a fresh schema about 1.5 years ago, we largely switched to the Oracular approach, and so we have very few resolvers beyond our root operations, and all of our asynchronous work occurs in one particular place in the code (heavy use of core.async, chatting with 10 - 20 different services or databases).