From 6134b1b0dbc84632affe6cf8317b12620edd596b Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:14:22 -0700 Subject: [PATCH 01/15] Add design principles draft --- design.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 design.md diff --git a/design.md b/design.md new file mode 100644 index 00000000000..4ee978ad275 --- /dev/null +++ b/design.md @@ -0,0 +1,38 @@ +# Design principles of the Apollo Client + +If we are building a client-side GraphQL client and cache, we should have some goals that carve out our part of that space. These are the competitive advantages we believe this library will have over others that implement a similar set of functionality. + +## Principles + +1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This should take precedence over performance optimizations. +2. Standalone - the published library should not depend on any specific build or runtime environment, view framework, router, philosophy, or other. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. +3. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". +4. Usable - given the above, developer experience should be a priority. The API for the developer should have a simple mental model, a minimal surface area, and be clearly documented. + +## Implementation + +I think the principles above place the following constraints on the implementation: + +### Stateless, well-documented store format + +All state of the GraphQL cache should be kept in a single immutable state object (referred to as the "store"), and every operation on the cache should be implemented as a function from the previous store object to a new one. The store format should be easy to understand by the application developer, rather than an implementation detail of the library. + +This will have many benefits compared to other approaches: + +1. Simple debugging/testing both of the Apollo client itself and apps built with it, by making it possible to analyze the store contents directly and step through the different states +2. Trivial optimistic UI using time-traveling and reordering of actions taken on the store +3. Easy integration of extensions/middlewares by sharing a common data interchange format + +To enable this, we need to have clear documentation about the format of this store object, so that people can write extensions around it and be sure that they will remain compatible. + +### Lowest-common-denominator APIs between modules + +APIs between the different parts of the library should be in simple, standard, easy-to-understand formats. We should avoid creating Apollo-specific representations of queries and data, and stick to the tools available - GraphQL strings, the standard GraphQL AST, selection sets, etc. + +If we do invent new data interchange APIs, they need to be clearly documented, have a good and documented reason for existing, and be stable so that plugins and extensions can use them. + +### Simple modules, each with a clear purpose + +There are many utilities that any smart GraphQL cache will need around query diffing, reading a cache, etc. These should be written in a way that it would make sense to use them in any GraphQL client. In short, this is a set of libraries, not a framework. + +Each module should have minimal dependencies on the runtime environment. For example, the network layer can assume HTTP, but not any other part. From aa98b6436d7564d66f07f93b8cc5af56cd8fc693 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:23:21 -0700 Subject: [PATCH 02/15] Add features --- design.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/design.md b/design.md index 4ee978ad275..1ef65764c18 100644 --- a/design.md +++ b/design.md @@ -4,10 +4,11 @@ If we are building a client-side GraphQL client and cache, we should have some g ## Principles -1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This should take precedence over performance optimizations. -2. Standalone - the published library should not depend on any specific build or runtime environment, view framework, router, philosophy, or other. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. -3. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". -4. Usable - given the above, developer experience should be a priority. The API for the developer should have a simple mental model, a minimal surface area, and be clearly documented. +1. Functional - the application developer should get real value out of using this library. It does real work other than just sending POST requests over the wire and returning the results. This work benefits both the application developer and the end user to achieve performance, usability, and simplicity of app implementation. +1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This should take precedence over fine-grained performance optimizations. +1. Standalone - the published library should not depend on any specific build or runtime environment, view framework, router, philosophy, or other. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. +1. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". +1. Usable - given the above, developer experience should be a priority. The API for the developer should have a simple mental model, a minimal surface area, and be clearly documented. ## Implementation From ba75a0a94c76ef079fe242037d1722f2ce8124ac Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:25:01 -0700 Subject: [PATCH 03/15] Concrete examples --- design.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/design.md b/design.md index 1ef65764c18..f603f65ad24 100644 --- a/design.md +++ b/design.md @@ -4,7 +4,7 @@ If we are building a client-side GraphQL client and cache, we should have some g ## Principles -1. Functional - the application developer should get real value out of using this library. It does real work other than just sending POST requests over the wire and returning the results. This work benefits both the application developer and the end user to achieve performance, usability, and simplicity of app implementation. +1. Functional - the application developer should get real value out of using this library. This benefits both the application developer and the end user to achieve performance, usability, and simplicity of app implementation. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). 1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This should take precedence over fine-grained performance optimizations. 1. Standalone - the published library should not depend on any specific build or runtime environment, view framework, router, philosophy, or other. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. 1. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". @@ -14,6 +14,17 @@ If we are building a client-side GraphQL client and cache, we should have some g I think the principles above place the following constraints on the implementation: +### Necessary features + +I think there is a "minimum viable" set of features for a good GraphQL client. Almost all GraphQL clients that aren't Relay don't have some of these features, and the necessity to have them is what requires people to buy into all of Relay. Based on talking to some developers, I believe that list includes: + +1. Optimistic UI for mutations +2. A cache so that you don't refetch data you already have +3. The ability to manually refetch data when you know it has changed +4. The ability to preload data you might need later +5. Minimal server roundtrips to render the initial UI +6. Query aggregation from your UI tree + ### Stateless, well-documented store format All state of the GraphQL cache should be kept in a single immutable state object (referred to as the "store"), and every operation on the cache should be implemented as a function from the previous store object to a new one. The store format should be easy to understand by the application developer, rather than an implementation detail of the library. From 0d55cffbd4fb8be5c172d1c6a5da65fca79cda99 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:26:19 -0700 Subject: [PATCH 04/15] Simplify text --- design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design.md b/design.md index f603f65ad24..f4864a5d91f 100644 --- a/design.md +++ b/design.md @@ -4,7 +4,7 @@ If we are building a client-side GraphQL client and cache, we should have some g ## Principles -1. Functional - the application developer should get real value out of using this library. This benefits both the application developer and the end user to achieve performance, usability, and simplicity of app implementation. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). +1. Functional - this library should bring benefits to an application's developers and end users to achieve performance, usability, and simplicity. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). 1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This should take precedence over fine-grained performance optimizations. 1. Standalone - the published library should not depend on any specific build or runtime environment, view framework, router, philosophy, or other. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. 1. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". From be7bf66bdb7489aa101db98e5e5d57a666908f66 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:26:51 -0700 Subject: [PATCH 05/15] Lead-in --- design.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/design.md b/design.md index f4864a5d91f..4a4ad581725 100644 --- a/design.md +++ b/design.md @@ -4,7 +4,9 @@ If we are building a client-side GraphQL client and cache, we should have some g ## Principles -1. Functional - this library should bring benefits to an application's developers and end users to achieve performance, usability, and simplicity. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). +The Apollo Client should be... + +1. Functional - this library should bring benefits to an application's developers and end users to achieve performance, usability, and simplicity. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). 1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This should take precedence over fine-grained performance optimizations. 1. Standalone - the published library should not depend on any specific build or runtime environment, view framework, router, philosophy, or other. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. 1. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". From eeec6e3795ca04a3433297c2ca814cc9a7d622ba Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:28:21 -0700 Subject: [PATCH 06/15] Clarity --- design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/design.md b/design.md index 4a4ad581725..75464f9b3f0 100644 --- a/design.md +++ b/design.md @@ -7,8 +7,8 @@ If we are building a client-side GraphQL client and cache, we should have some g The Apollo Client should be... 1. Functional - this library should bring benefits to an application's developers and end users to achieve performance, usability, and simplicity. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). -1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This should take precedence over fine-grained performance optimizations. -1. Standalone - the published library should not depend on any specific build or runtime environment, view framework, router, philosophy, or other. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. +1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This principle should take precedence over fine-grained performance optimizations. +1. Standalone - the published library should not impose any specific build or runtime environment, view framework, router, or development philosophies other than JavaScript or GraphQL. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. 1. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". 1. Usable - given the above, developer experience should be a priority. The API for the developer should have a simple mental model, a minimal surface area, and be clearly documented. From cd3e52fe54fe2d0f170d0175e8f33b36542442db Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:28:56 -0700 Subject: [PATCH 07/15] NPM is important --- design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design.md b/design.md index 75464f9b3f0..dfef739124f 100644 --- a/design.md +++ b/design.md @@ -8,7 +8,7 @@ The Apollo Client should be... 1. Functional - this library should bring benefits to an application's developers and end users to achieve performance, usability, and simplicity. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). 1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This principle should take precedence over fine-grained performance optimizations. -1. Standalone - the published library should not impose any specific build or runtime environment, view framework, router, or development philosophies other than JavaScript or GraphQL. When you install it via NPM, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. +1. Standalone - the published library should not impose any specific build or runtime environment, view framework, router, or development philosophies other than JavaScript, NPM, or GraphQL. When you install it via NPM in any NPM-compatible development environment, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. 1. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". 1. Usable - given the above, developer experience should be a priority. The API for the developer should have a simple mental model, a minimal surface area, and be clearly documented. From 366c10365d35aa8fde352166465e5eff5887678a Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:29:36 -0700 Subject: [PATCH 08/15] Should work --- design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design.md b/design.md index dfef739124f..a41bdd663e9 100644 --- a/design.md +++ b/design.md @@ -9,7 +9,7 @@ The Apollo Client should be... 1. Functional - this library should bring benefits to an application's developers and end users to achieve performance, usability, and simplicity. It should have more features than [Lokka](https://github.com/kadirahq/lokka) but less than [Relay](https://github.com/facebook/relay). 1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This principle should take precedence over fine-grained performance optimizations. 1. Standalone - the published library should not impose any specific build or runtime environment, view framework, router, or development philosophies other than JavaScript, NPM, or GraphQL. When you install it via NPM in any NPM-compatible development environment, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. -1. Compatible - the Apollo Client should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but as much as possible it should "just work". +1. Compatible - the Apollo Client core should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but it should "just work" even in the absence of those features. 1. Usable - given the above, developer experience should be a priority. The API for the developer should have a simple mental model, a minimal surface area, and be clearly documented. ## Implementation From 28c8c214d8282566efcdb2e3580711495c3ec005 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:29:53 -0700 Subject: [PATCH 09/15] App dev --- design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design.md b/design.md index a41bdd663e9..80ecd37849d 100644 --- a/design.md +++ b/design.md @@ -10,7 +10,7 @@ The Apollo Client should be... 1. Transparent - a developer should be able to keep everything the Apollo Client is doing in their mind at once. They don't necessarily need to understand every part of the implementation, but nothing it's doing should be a surprise. This principle should take precedence over fine-grained performance optimizations. 1. Standalone - the published library should not impose any specific build or runtime environment, view framework, router, or development philosophies other than JavaScript, NPM, or GraphQL. When you install it via NPM in any NPM-compatible development environment, the batteries are included. Anything that isn't included, like certain polyfills, is clearly documented. 1. Compatible - the Apollo Client core should be compatible with as many GraphQL schemas, transports, and execution models as possible. There might be optimizations that rely on specific server-side features, but it should "just work" even in the absence of those features. -1. Usable - given the above, developer experience should be a priority. The API for the developer should have a simple mental model, a minimal surface area, and be clearly documented. +1. Usable - given the above, developer experience should be a priority. The API for the application developer should have a simple mental model, a minimal surface area, and be clearly documented. ## Implementation From 0bb52dce18228db56b8da361451551c16bc79f92 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:30:09 -0700 Subject: [PATCH 10/15] Lead to --- design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design.md b/design.md index 80ecd37849d..f89a6312e2d 100644 --- a/design.md +++ b/design.md @@ -14,7 +14,7 @@ The Apollo Client should be... ## Implementation -I think the principles above place the following constraints on the implementation: +I think the principles above naturally lead to the following constraints on the implementation: ### Necessary features From 1f03702a7d3d991ee95a53c5084384c077a13abe Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:31:41 -0700 Subject: [PATCH 11/15] Being able to inspect the store is critical --- design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design.md b/design.md index f89a6312e2d..03116de2210 100644 --- a/design.md +++ b/design.md @@ -29,7 +29,7 @@ I think there is a "minimum viable" set of features for a good GraphQL client. A ### Stateless, well-documented store format -All state of the GraphQL cache should be kept in a single immutable state object (referred to as the "store"), and every operation on the cache should be implemented as a function from the previous store object to a new one. The store format should be easy to understand by the application developer, rather than an implementation detail of the library. +All state of the GraphQL cache should be kept in a single immutable state object (referred to as the "store"), and every operation on the store should be implemented as a function from the previous store object to a new one. The store format should be easily understood and inspected by the application developer, rather than an implementation detail of the library. This will have many benefits compared to other approaches: From 699f7461c1a099d0d9e1ca7557884d0e01a13eaf Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:36:13 -0700 Subject: [PATCH 12/15] Pagination --- design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/design.md b/design.md index 03116de2210..4f208f99a1e 100644 --- a/design.md +++ b/design.md @@ -26,6 +26,7 @@ I think there is a "minimum viable" set of features for a good GraphQL client. A 4. The ability to preload data you might need later 5. Minimal server roundtrips to render the initial UI 6. Query aggregation from your UI tree +7. Basic handling of pagination, most critically being able to fetch a new page of items when you already have some ### Stateless, well-documented store format From e82a2dca34432a62e44c2a595757de85441ba893 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:37:48 -0700 Subject: [PATCH 13/15] Clarify point about Relay --- design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design.md b/design.md index 4f208f99a1e..0b26541352e 100644 --- a/design.md +++ b/design.md @@ -18,7 +18,7 @@ I think the principles above naturally lead to the following constraints on the ### Necessary features -I think there is a "minimum viable" set of features for a good GraphQL client. Almost all GraphQL clients that aren't Relay don't have some of these features, and the necessity to have them is what requires people to buy into all of Relay. Based on talking to some developers, I believe that list includes: +I think there is a "minimum viable" set of features for a good GraphQL client. Almost all GraphQL clients that aren't Relay don't have some of these features, and the necessity to have them, even when they don't have many other requirements, is what drives people to use Relay which often brings more functionality and complexity than they need for their application. Based on talking to some developers, I believe that list includes: 1. Optimistic UI for mutations 2. A cache so that you don't refetch data you already have From 6d604b12f893691f6b197310a1f986ac9266f7a9 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Sat, 19 Mar 2016 16:41:22 -0700 Subject: [PATCH 14/15] Link to slide --- design.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/design.md b/design.md index 0b26541352e..d3ce71cfc30 100644 --- a/design.md +++ b/design.md @@ -18,7 +18,9 @@ I think the principles above naturally lead to the following constraints on the ### Necessary features -I think there is a "minimum viable" set of features for a good GraphQL client. Almost all GraphQL clients that aren't Relay don't have some of these features, and the necessity to have them, even when they don't have many other requirements, is what drives people to use Relay which often brings more functionality and complexity than they need for their application. Based on talking to some developers, I believe that list includes: +I think there is a "minimum viable" set of features for a good GraphQL client. Almost all GraphQL clients that aren't Relay don't have some of these features, and the necessity to have them, even when they don't have many other requirements, is what drives people to use Relay which often brings more functionality and complexity than they need for their application. Bringing us to [this graph from React Conf](https://www.dropbox.com/s/kppd4kdz40h96kj/Screenshot%202016-03-19%2016.40.19.png?dl=0) ([full slides here](https://github.com/jaredly/reactconf)). + +Based on talking to some developers, I believe that list includes: 1. Optimistic UI for mutations 2. A cache so that you don't refetch data you already have From cf83c5ffdf9bdbae59717fcb153b735d9cb67366 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Mon, 21 Mar 2016 17:40:15 -0700 Subject: [PATCH 15/15] Unordered --- design.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/design.md b/design.md index d3ce71cfc30..425b3a77563 100644 --- a/design.md +++ b/design.md @@ -20,15 +20,17 @@ I think the principles above naturally lead to the following constraints on the I think there is a "minimum viable" set of features for a good GraphQL client. Almost all GraphQL clients that aren't Relay don't have some of these features, and the necessity to have them, even when they don't have many other requirements, is what drives people to use Relay which often brings more functionality and complexity than they need for their application. Bringing us to [this graph from React Conf](https://www.dropbox.com/s/kppd4kdz40h96kj/Screenshot%202016-03-19%2016.40.19.png?dl=0) ([full slides here](https://github.com/jaredly/reactconf)). -Based on talking to some developers, I believe that list includes: - -1. Optimistic UI for mutations -2. A cache so that you don't refetch data you already have -3. The ability to manually refetch data when you know it has changed -4. The ability to preload data you might need later -5. Minimal server roundtrips to render the initial UI -6. Query aggregation from your UI tree -7. Basic handling of pagination, most critically being able to fetch a new page of items when you already have some +Based on talking to some developers, I believe that list includes, in no particular order: + +- Optimistic UI for mutations +- A cache so that you don't refetch data you already have +- The ability to manually refetch data when you know it has changed +- The ability to preload data you might need later +- Minimal server roundtrips to render the initial UI +- Query aggregation from your UI tree +- Basic handling of pagination, most critically being able to fetch a new page of items when you already have some + +The implementation process will determine the order in which these are completed. ### Stateless, well-documented store format