
GraphQL Fragments Are the Best Match for UI Components - samerbuna
https://blog.manifold.co/graphql-fragments-are-the-best-match-for-ui-components-72b8f61c20fe
======
exogen
One thing I keep running into is that for a few of my app ideas, I'm not
really interested in just having static queries known at build time, like
Relay and Apollo are designed for. I specifically want them to be dynamic and
actually based on what components get rendered.

Consider this query from the article:

    
    
        query ProfilePageData {
          user(handle: "manifoldco") {
            ...HeaderData
            ...SidebarData
            ...TweetListData
          }
        }
    

That's great if the ProfilePage component knows that it only contains the
Header, Sidebar, and TweetList components. Since they're designed for static
queries, both Relay and Apollo suffer from this shortcoming: the parent query
needs to know about every component that could possibly add fragments ahead of
time, and pull them all in. In my opinion, this really fails at fulfilling the
promise of components – the parent shouldn't need to know what all its
descendants do.

What if the rendered components are more dynamic? Putting @skip directives on
every field is not really an option (and then you need a matching query
$variable for every directive.)

If you think of the query as more of a template, then you could have portions
of the query that are like "template blocks" that other components could
extend, e.g.:

    
    
        query ProfilePageData {
          user(handle: "manifoldco") {
            ${userFragments}
          }
        }
    

...then descendant components could have access to `userFragments` as an
extension point. I'm not yet sure if it's a terrible idea, but I started a
project just the other day to experiment with it:
[https://github.com/exogen/apollo-dynamic-
queries](https://github.com/exogen/apollo-dynamic-queries)

~~~
atticusberg
It sounds like your child components should be fetching their own data via
their own graphql queries rather than consuming it as props from the parent
that's rendering them.

That way the parent can just render whatever components it wants without
having to worry about what data they require.

~~~
exogen
I've considered that, but it's extremely annoying (and still not really
possible with Relay or Apollo). Not only would I be relying on Apollo's query
batching/merging in order to not make 100 different queries over the network,
but each child component would need to duplicate the base query _and
variables_ , meaning they'd need the props that correspond to query variables
passed all the way down the component tree to them.

Consider the Artist.Name and Artist.Disambiguation components in my GitHub
example. Notice how they don't need the `mbid` prop that the ancestor
component provides. I want a component like that for every single field in my
Artist schema. If every one of those components duplicated the artist query,
they'd all need to be passed the `mbid`, because that's how it determines what
artist to retrieve. It's possible to do some tricks with `context` like I'm
doing now, but I don't really trust that it's a better solution than having
one query execution point with an extendable query.

~~~
gjjrfcbugxbhf
You can have child components that get some data from props and some from
their own query. Just inject the props into the relay renderer.

~~~
exogen
Yes, I'm saying that getting those props to those children in the first place
is annoying to do and I'd still have to build a bunch of `context` helpers on
top of Relay and Apollo to make doing that nice.

This is the API I want:

    
    
        <Artist mbid="abc-123">
          <Artist.Name />
          <Artist.Disambiguation />
          <SomeOtherComponentThatHasArtistFieldDescendants />
        </Artist>
    

This is the API you're saying I would have to use with vanilla Relay and
Apollo:

    
    
        <Artist mbid="abc-123">
          <Artist.Name mbid="abc-123" />
          <Artist.Disambiguation mbid="abc-123" />
          <SomeOtherComponentThatHasArtistFieldDescendants mbid="abc-123" />
        </Artist>

~~~
exogen
Assuming I did build HOCs for doing so, the difference boils down to this: in
your approach, query variable props would magically be passed _down_ the
component tree via `context` and used by descendant components in duplicated
queries. The way I've built it, query fragments are magically passed _up_ the
component tree via `context` and used in a single query.

Considering neither are possible with vanilla Relay and Apollo and I need to
build these helper HOCs either way, I don't really see why what you're
proposing is better. I had already considered them both and deliberately did
_not_ do it that way.

------
otto_ortega
I really love this approach, I have been thinking about it for some time
now... However, as much as I like it, it seems that implementing a GraphQL
server is not an easy task, and getting an in-depth understanding of how
GraphQL works seems quite challenging.

I can devote a few days to read the JSON-API spec
([http://jsonapi.org/](http://jsonapi.org/)) and get a pretty good
understanding of it. I wish there were a way to consume JSON-API based APIs in
the same declarative way GraphQL provides.

I'm thinking that a library that "translates" GraphQL queries to JSON-API
requests will be a great solution.

~~~
petetnt
GraphQL is something that's relatively simple (and brilliant) but is one of
those things that you just need to try properly first for it to click,
especially if you have a background of implementing tons of REST based
services.

The GraphQL related tools are top notch and implementing GraphQL server isn't
any harder than a REST based server would be (one could even argue that it
would be simpler). Even if you don't want to implement everything from
scratch, tools like Postgraph[0] exists that pretty much automate it for you.

I made a simple tutorial[1] on wrapping an existing API with GraphQL here
which goes through basics of settings up too.

[0]
[https://github.com/postgraphql/postgraphql](https://github.com/postgraphql/postgraphql)
[1] [https://github.com/motleyagency/devday-
tutorials/blob/master...](https://github.com/motleyagency/devday-
tutorials/blob/master/docs/02_graphqlify.md)

~~~
otto_ortega
Thank you, I will be checking on Postgraph and your tutorial, I'm really
interested in adding GraphQL to my development stack.

------
KirinDave
Sure, but you're asking your server to support a full query language. Even
with data-fetch on a js-impl (or ... a hell of a lot of by-hand tooling in
Python with Graphene), this overhead really hurts your qps.

To be honest, I sort of wonder if the right place to implement the GQL
interpretation and scheduling layer is in a service-worker.

That way, the client devs who love this complex query language can also
support the queries they need and ensure their calling conventions don't
violate performance boundaries in the gql server implementation.

~~~
andrewingram
It's really not that hard to build a relatively optimal GraphQL server.
Obviously it requires paying careful attention to performance, and to take
steps to mitigate abuse (query whitelists, complexity caps etc). If you were
to move the implementation off the server and into the client you'd use a lot
of the benefits of GraphQL in the first place (performance being a major one).

~~~
rhizome
_Obviously it requires paying careful attention to performance, and to take
steps to mitigate abuse (query whitelists, complexity caps etc)_

Are these also "not that hard?"

~~~
arianon
"Query whitelists" sounds like sending to the server something like
`{"query_id": 4, "variables": ...}` instead of `{"query": ..., "variables":
...}` which are straightforward to implement using any kind of server side
key-value store and a middleware that maps the `query_id` back to the
corresponding `query`, a tool that can help you with this is Apollo's
PersistGraphQL [1]

I have no idea how I would go about implementing complexity caps though, but I
guess I would do something like what GitHub has done for their own GraphQL API
[2], which they explain better than I can.

[1]:
[https://github.com/apollographql/persistgraphql](https://github.com/apollographql/persistgraphql)
[2]: [https://developer.github.com/v4/guides/resource-
limitations/](https://developer.github.com/v4/guides/resource-limitations/)

~~~
exogen
Another simple option for limiting complexity (I've considered implementing
this in my GraphBrainz project): in the `context` provided to the GraphQL
query resolver, increment a counter whenever a resolver requires fetching from
an external API/database/etc. (whatever "too much of" would constitute abuse
or just take a long time). Fail if the counter reaches some threshold. This
would be really easy.

Also, instead of multiplying node counts like GitHub does (which is pretty
clever!), another simple option would be to look at the depth of the query
(how many levels down is the deepest leaf), and fail if it's over some
maximum. This is also very easy to do as you get the query AST in the `info`
field of the resolver. (This one is less effective than the one above since
depth doesn't totally match up with resource usage, fields can be aliased,
etc. but you get the idea.)

~~~
KirinDave
> Another simple option for limiting complexity

Okay but... I guess my question is: why are you denying a client the right to
make a complex query? Is it because all your queries are kinda slow and so you
must hand-optimize them, leading to a combinatoric explosion of codepaths?

Or is it because your clients cannot judge how complex the queries they're
making are? If so, isn't this actually a gap in the GQL spec? Lots of other
query language implementations offer query description and estimation commands
in their code.

Your proposed solution seems to me like it's brutal for your consumers.
There's minimal indication of how quickly your complexity metric will rise in
the query. You'd need to add ad-hoc per query&mutation arguments to push that
query complexity cap up for legitimate uses.

~~~
exogen
> Okay but... I guess my question is: why are you denying a client the right
> to make a complex query? Is it because all your queries are kinda slow and
> so you must hand-optimize them, leading to a combinatoric explosion of
> codepaths?

No, it's because:

(1) This is a feature of literally every API, most of them just use the
extremely blunt instrument of rate limiting (even if requesting the same
simple scalar value field over and over again does not add any strain on the
server, you'll be rate limited just the same). Why aren't you asking this same
question about REST queries?

and

(2) The 'Graph' part of 'GraphQL' means that queries can theoretically request
connected nested objects of nearly infinite depth. This doesn't require that
anything about the query code be slow or needs to be hand-optimized, or that
there be any complex codepaths, just that MORE JOINS == MORE WORK and MORE
PAYLOAD, no matter how perfectly optimized it is. Why aren't you asking "why
does REST deny clients the right to make as deeply nested queries as they
need?"

~~~
KirinDave
> This is a feature of literally every API... Why aren't you asking this same
> question about REST queries?

Combinatoric explosions of complexity via a single query path are not a
feature of every API.

> The 'Graph' part of 'GraphQL' means that queries can theoretically request
> connected nested objects of nearly infinite depth.

Thanks for this.

> just that MORE JOINS == MORE WORK and MORE PAYLOAD

So like SQL but without all the excellent query complexity tools or clarity
around what precipitates a join?

> Why aren't you asking "why does REST deny clients the right to make as
> deeply nested queries as they need?"

Because RESTful APIs tend not to allow ad hoc graph traversal. When they do,
it's because they're tunneling a graph query language. When they do (e.g.,
ElasticSearch) I (and we, as in the community at large)_DO ask these
questions.

~~~
exogen
> Combinatoric explosions of complexity via a single query path are not a
> feature of every API.

> Because RESTful APIs tend not to allow ad hoc graph traversal.

I think you're taking this graph part too literally. Almost every API has a
"graph" of connected objects. GraphQL just makes it so that you can traverse
them with a single query. REST endpoints tend to force you to make multiple
queries to go back and fetch information about the entities whose IDs or URLs
you received in earlier requests – thus the rate limiting. In both cases,
combinoratic explosions (and infinite depth) are possible – REST just forces
you to explode into more round-trips (and the server is likely doing even more
duplicated work than it needs to to fulfill those subsequent requests).

If you wanted to simulate the ease-off aspect of REST requiring clients to
return for multiple rate-limited round trips to get the query data they want,
you could simply add a timeout in the nested object's GraphQL resolvers that
perform self-rate-limiting. Same result but the clients don't need to know
about it, they can just wait the same amount of time they'd have had to wait
for all the data anyway.

~~~
KirinDave
> GraphQL just makes it so that you can traverse them with a single query.

Yes. This is what I'm saying. GQL allows for a combinatoric explosion of
potentially required queries (and in extreme cases, data providers) to fufill
any request. And every GQL endpoint needs to be able to service all of them
unless your request routing proxy can peek into body contents, which is more
expensive than URL routing.

> REST endpoints tend to force you to make multiple queries to go back and
> fetch information about the entities whose IDs you received in earlier
> requests

A problem we can solve elegantly with HTTP/2 push using nearly identical
underlying API servicing models. What's great about that approach is that it's
totally transparent to the client; they just get better performance with less
resources.

Instead, folks have decided to discard a lot of really positive aspects of the
REST model to make a client-facing DSL realized in the server.

> In both cases, combinoratic explosions (and infinite depth) are possible

But in the classical rest case, the client is aware they're doing this, as
well as the server. In the GraphQL case, we've obfuscated this and said, "We
reserve the right to reject your quest for any reason, and we've also made it
harder for us to service your query (unless we go back to mandating every
valid query as in rest), and we've also made scaling harder because it's more
difficult to factor endpoints into different scaling groups."

But hey, that DSL is great. It's like JSON without tall that predictability or
syntactic validation.

I cannot see any positive outcomes to adopting graphql other than that,
"Client-side developers love it". If ya'll love it so much, why not maintain
it on your side via service-worker query interception?

I ask facetiously. The answer is: because that would be really hard, and we'd
rather push it off to API endpoint devs. Devs who promptly put restrictions
that basically render the best part of GraphQL (that it is a query language)
impotent for performance reasons.

~~~
exogen
> I cannot see any positive outcomes to adopting graphql

How about one HTTP request often being faster than multiple requests? How
about only retrieving the payload you requested rather than all the extra data
the API developers decided to expose in the endpoint – bandwidth isn't free?
How about not having to add new custom bespoke API endpoints because some new
part of the website just needs a few little different pieces of data that
would normally require several round trips, pretty please? These are just
normal everyday issues that people put up with when using REST APIs.

> A problem we can solve elegantly with HTTP/2 push using nearly identical
> underlying API servicing models. What's great about that approach is that
> it's totally transparent to the client; they just get better performance
> with less resources.

Shouldn't you use push when you know the client will ask for the resources?
How would you know whether the client will ask for certain connected objects
in this case? Would you just always be pushing every connected object over the
wire?

I don't really see why you're giving such special distinction to one HTTP
request vs. multiple HTTP requests. That is an arbitrary distinction to make.
You shouldn't be asking "how much strain can a client put on my server with a
single request? but oh, they can make multiple requests…" but "how much strain
is a client going to put on my server to get all the data they need, whether
it happens across one request or multiple?"

~~~
KirinDave
> How about one HTTP request often being faster than multiple requests?

Http/2 push.

But also: would it actually be more dev hours to write out the custom RESTful
queries? If you're whitelisting individual queries. What's the difference
then? You've just got a more awkward, uncachable, less split-routable protocol
for exactly the same data.

> Shouldn't you use push when you know the client will ask for the resources?

Yes. If I know they intend to join data in, I can push it. I can even do this
somewhat speculatively based on statistical patterns in clients. I can tune
those values based on real data which can be refined over time.

~~~
exogen
> Yes. If I know they intend to join data in, I can push it.

Right, so if you don't have the full "query", which you don't with multiple
REST round-trips, then you won't push it...

> I can even do this somewhat speculatively based on statistical patterns in
> clients. I can tune those values based on real data which can be refined
> over time.

Cool, so guessing. That's exactly what I want my API's performance profile to
be based on. Sounds like a lot of work man, why don't you just use GraphQL
instead? ;)

~~~
KirinDave
> Right, so if you don't have the full "query", which you don't with multiple
> REST round-trips, then you won't push it... well.

Yes. But of course, GraphQL ad hoc extensions are discussing limiting this
arbitrarily as well.

> Cool, so guessing. That's exactly what I want my API's performance profile
> to be based on.

No. For example, if I can say that a banking customer wants to see a second
page of transactions 90% of the time, then I should push the next page every
time. If I can say they want to see the third page of transactions 10% of the
time, then it makes sense to defer the cost.

------
kotojo
Question for someone with experience with graphql. I have a data heavy react
application I'm working on that would benefit from something like this, but
almost every single component has the ability to refetch it's data
individually. If you have all of these fragments being passed up to the single
query being called, how do you handle a situation where you want the
explicitness of refetching only one of those fragments, but getting it on in
one initial call?

~~~
brokentone
It's less about component-level refetching (though you can), it's more about
the strong data specificity and composition that is the point of this article.
That said, it's up to your query builder to figure out intelligent refreshing
/ refetching. I use Relay, and it's pretty good at this.

------
jmull
I don’t understand... doesn’t GraphQL encourage embedding full queries into
the client app? It’s not SQL but it’s equivalent. Isn’t this a road of pain
for any project that is long-lived or scaled.

I guess I can see it sitting between a data cache — that is filled by a set of
relatively large-granularity fixed queries that can have a decent cache
validation mechanism — and the client UI. Maybe that’s how it’s actually
implemented? But then it seems like that would often be better (at least much
of the time) for that data cache to be client-side.

You do want a way to pass _user_ filter conditions back through the data
access pipeline (but not business logic where-clause conditions) all the way
back to the data store, but those should still be constrained.

------
mattmurdog
The one thing I never got with GraphQL is what do you query if you don't know
what to query for??? Can someone answer this for me.

I don't know what I need. Server tells me I need {allthedogs}. Now I know to
query for {allthedogs}... what's the point of GraphQL?

~~~
exogen
The schema should still be documented somewhere, like any API. But even if
it's not: GraphQL supports a special "introspection query" that will tell you
the entire schema that you can query. Pointing a tool like GraphiQL
([https://github.com/graphql/graphiql](https://github.com/graphql/graphiql))
at a GraphQL endpoint will even run the introspection query automatically and
turn the result into rendered explorable documentation. That's how you figure
out what to query.

------
ojr
I dont use fragments, I feel if I need a fragment, the jsx file is too bloated
just make more components and higher order components that connect with a
small slice of your graphl queries. This is probably causes more duplication
but I'll copy and paste if it makes things easier to read

------
akmiller
This is why Om Next (in cljs) with datomic backend is so powerful!

~~~
erichmond
I was curious if anyone was going to mention this. We had been looking at
GraphQL for our latest project, but realized datomic + pull patterns + re-
frame was a more integrated fit.

I hate jacking threads pointing out X instead of Y, but in this case it feels
warranted.

~~~
sorenbs
That's exactly right. GraphQL is a contract between backend and frontend. I
tend to think of the GraphQL schema as documentation that is always up to
date.

------
avodonosov
om.next does a similar thing

------
fenollp
Add some ML to extract common UX patterns and generate highly personalized
websites/apps.

The only job of the future will be AI-assisted design.

~~~
gipp
Machine learning is not pixie dust.

~~~
cat199
But what if you added some ML to extract common UX patterns to it hmm?

~~~
weego
Sounds like the perfect place to use a cryptocurrency as a distributed query
ledger.

