facebook / relay

Relay is a JavaScript framework for building data-driven React applications.
https://relay.dev
MIT License
18.23k stars 1.8k forks source link

Reactive GraphQL Architecture #4687

Open captbaritone opened 1 month ago

captbaritone commented 1 month ago

Reactive GraphQL Architecture

This document outlines a vision for using GraphQL to model client data in applications which have highly complex client state. It is informed by the constraints of developing applications for the web, but should be applicable to native applications as well.

GraphQL provides a declarative syntax for application code to specify its data dependencies. While GraphQL was designed for facilitating query/response communications between clients and servers, it has also proved a useful mechanism for implementing client-side data loading from non-GraphQL servers. Implementing your client-side data layer as a GraphQL executor enables decoupling product code from the code which fetches data from a REST server. The GraphQL resolver architecture also provides an opinionated way to model the data layer, forcing it to be implemented in composable fashion, where the GraphQL executor is responsible for composing the individual resolvers together to derive all the needed data for a product surface.

Historically, this architecture is most often encountered in products where the front-end team sees value in the developer experience of GraphQL, but organizational or technical impediments prevent implementing the GraphQL executor on the server. However, we are starting to see other types of applications where this architecture makes sense for purely technical reasons. Examples include:

While implementing a GraphQL executor on the client can be an attractive architecture from a developer experience perspective, it creates a number of challenges in terms of efficiency. The rest of this post will describe a proposed evolution of this architecture which preserves its benefits while mitigating many of its challenges.

The Architecture

While the architecture is not prescriptive about any actual tools, Relay, with its compiler and generated code, is well positioned to explore this architecture. Ideally it can eventually be decomposed into distinct tools:

Problems Solved

Benefits Preserved

Open Questions

We are still early in exploring this architecture and some open questions remain:

Collaboration Opportunities

Sources

These ideas have been explored across various projects:

alloy commented 1 month ago

What should the semantics of mutations be in the context of a reactive GraphQL executor? Specifically, the response portion of the mutation is generally used to specify which updates the client would like to observe, but with a reactive executor we already expect to be notified of changes to any data we are currently observing.

In a world where we'd want to ideally eliminate all updaters, optimistic updates will need to be handled by the local data-store layer. Would this become entirely a concern of the application, or do you imagine Relay would still play a role in this?

alloy commented 1 month ago

Are there viable migrations strategies to incrementally adopt this architecture when coming from other existing setups?

This includes:

flow-danny commented 1 month ago

I was actually thinking about doing a little experiment implementing a Network layer fetchQuery that doesn't really fetch over HTTP, but gets data from local SQLite.

It would do this by running graphql resolvers as if it's a graphql server, avoiding all intermediate serialization.

After every commit from network updates, a crude way to make it reactive, could be to invalidate the Store?

captbaritone commented 4 weeks ago

In a world where we'd want to ideally eliminate all updaters, optimistic updates will need to be handled by the local data-store layer. Would this become entirely a concern of the application, or do you imagine Relay would still play a role in this?

Still an open question! Perhaps the answer is that you'll be able to do either? Some client data layers may have their own optimistic state mechanism. That would probably have the ability to be more robust. A higher ceiling.

Conversely some will not, in which case Relay's primitive which make sense for server data should still be available.

captbaritone commented 4 weeks ago

I was actually thinking about doing a little experiment implementing a Network layer fetchQuery that doesn't really fetch over HTTP, but gets data from local SQLite.

I did a prototype of something similar to this using Relay Resolvers which you can find here: https://relay.dev/docs/next/guides/relay-resolvers/introduction/

By using Relay Live Resolvers (experimental feature) you can invalidate values at a field or record granular level. For a crude start I just invalidated every value on every db update. But something like https://github.com/vlcn-io/cr-sqlite could probably get you something much more sophisticated.

flow-danny commented 4 weeks ago

With a generic entity schema it will be easy to know which Node IDs are invalid, but is there currently a way to invalidate only the active queries currently rendering those nodes?

flow-danny commented 4 weeks ago

I also looked at Relay resolvers, but they work so differently to normal resolvers... tied to fragments instead of the schema itself.

Skipping all the networking and JSON back and forth, a regular graphql-tools resolver will already give you subscriptions, which is basically reactive.

I'm sure its possible to stitch a server schema in there and have the client-side resolver do a regular network fetch.

The resolver could also be compiled using something like graphql-jit to reduce overhead.

captbaritone commented 3 weeks ago

I also looked at Relay resolvers, but they work so differently to normal resolvers... tied to fragments instead of the schema itself.

Sorry for the confusion. We've been expanding Relay Resolvers to enable them to model arbitrary arbitrary client state with field-level reactivity. I've just merged a PR which add documentation for this experimental feature. You can read more here: https://relay.dev/docs/next/guides/relay-resolvers/introduction/

Lalitha-Iyer commented 1 week ago

Abstracts away the distinction between server and client data for product code enabling a single declarative API for reading data.

Would this mean we would continue to use client state management libraries like Redux but provide a common abstraction for querying?