graphql

hlship 2022-03-08T22:10:32.035919Z

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.

hlship 2022-03-08T22:11:36.062639Z

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.

hlship 2022-03-08T22:12:08.022939Z

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

hlship 2022-03-08T22:12:28.958189Z

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

hlship 2022-03-08T22:13:55.522459Z

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.

hlship 2022-03-08T22:15:16.959029Z

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).

hlship 2022-03-08T22:16:21.511989Z

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.

hlship 2022-03-08T22:18:29.350429Z

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).