A maintainable application architecture requires that the UI only contain the rendering logic and execute queries and mutations against the underlying data model on the server. A maintainable architecture must not contain any logic for managing "app state" on the client (other than client-only state for generic input validation or animation which is encapsulated by individual UI components or HOCs), as either would necessarily embed business logic in the client and introduce cache invalidation challenges. App state should be derived directly from the server, and in realtime for highly interactive UX.
With GraphQL we are able to define an easy-to-change application-level data schema on the server that captures the types and relationships in our data, and wiring it to data sources via resolvers that leverage our db's own query language (or data-oriented, uniform service APIs) to resolve client-specified "queries" and "mutations" against the schema.
We use GraphQL queries to dynamically derive a UI-specific mapping of app state from server-side data stores to client that can be updated in real time (see FeathersJS below.)
With this approach, the developer's job becomes a much more pleasant and simpler task of building the application-level GraphQL schema, building UI components and declaratively specifying the required queries and mutations.
Client-centric architectures (aka shallow architectures) tend to derive and/or cache "app state" on the client, and as such end up either replicating business logic in the client or managing cache invalidation at granular level. While those architectures enable things like 'optimistic UI' and 'offline first' apps they can exponentially complicate the work involved in maintaining and evolving the application.
This meta-framework is designed for realtime, transactional apps (think: ordering & delivery and 'hard realtime' apps Uber) In this context, what does it mean to tell the user that their order was successful or the driver is 2 mins away then finding out from server that the order cannot be fulfilled or the driver is stuck in traffic? So offline-first and optimistic updates are at odds with 'realtime' and present extra overhead in 'transactional' apps where things are changing all the time. It is said that cache invalidation (a cache, aka client-side app state container, is necessary for optimistic and offline-first UI) is one of the three hardest problems in computer science. So we definitely gain agility and simplicity by removing the requirement for offline-first and optimistic UI. So then the question is wouldn't thast hurt mobile performance? The fact is that GraphQL already does a lot to help mobile performance by allowing us to get exactly the data we need at any given instance as opposed to dowloading more data than we need and putting it in the client side cache (aka client-side app state container) and complicating the app by having to manage app state on client and server instead of simply deriving it from server in real time.
-
Avoid business logic in the client: “app state” should not be composed and/or cached on the client as that would put redundant business logic in the UI and/or lead to cache-invalidation challenges both of which add a lot of unneeded complexity to the app. The UI needs to be just an I/O layer, fetching/re-fetching UI-component-bound query results and making mutations to data on the server, leveraging an application-level, UI-agnostic schema on the server. In this state-free, functional UI model, local component state (including local state of higher order components) is used for ephemeral, client-only state such as animation state. We avoid caching and/or composing app state on the client because that ultimately leads to cache invalidation challenges, not to mention embedding business logic in the UI, both of which greatly complicate things. Instead, we opt to simply refetch UI-component-bound query results based on certain events like (backend mutation events, navigation state change or after network interruption) and let UI components derive their own state from the server based on bound query results. The business logic stays on the server and the UI developer's job becomes very simple.
-
The need for pre-mutation hooks: we need to be able to authorize the user before executing a mutation (and conditionally avoiding the mutation) so we can implement authorization on the server and leave business logic completely outside the client.
-
The need for post-mutation hooks to make calls to external APIs (e.g. send email) after a given mutation.
-
The need for live data subscriptions that perform well at scale: when something changes on the server, we need to know about it, either via polling or subscriptions. The latter has the advantage of being immediate.
-
Currently, GraphQL developers need to know many different querying syntaxes for the various resolvers in the schema (MySQL, MongoDB, different APIs, etc), or write their own API adapters for each data store to decouple their resolvers from the underlying data store.
-
We should be able to validate data based on our business logic, inside GraphQL resolvers
-
We should be able to implement secure authentication using OAuth and Email/Password.
-
We should be able to implement authorization independent of our database or network interface.
-
We should be able to implement validation generically at the UI component level for instant feedback as user types
-
We should be able to compose business logic in GraphQL resolver using microservices with a uniform data-oriented interface.
###References
- [Building a GraphQL Server with Node.js and SQL] (https://www.reindex.io/blog/building-a-graphql-server-with-node-js-and-sql/)
Repo coming soon...
###Update
@gaearon (Dan Abramov), author of Redux, was gracious enough to tweet this, and I give him a lot of credit for doing so.
Here is a section of the very long conversation that Dan started:
![twitter conversation] (http://i.imgur.com/BD2Wu5m.png)
###Update 3:
My follow up conversation with Dan Abrahmov below
![twitter follow up] (http://i.imgur.com/43lsGJo.png)
