Hacker News new | ask | show | jobs
GraphQL Fragments Are the Best Match for UI Components (blog.manifold.co)
136 points by samerbuna 3159 days ago
10 comments

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

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.

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

> I've considered that, but it's extremely annoying (and still not really possible with Relay or Apollo)

It’s easy with Relay. With Classic you render a RelayRenderer. In Relay Modern you render a QueryRenderer. Either way, you can issue more queries than just your root query and it’s as easy as rendering a React component.

It’s not. I’ve tried with Relay Modern. See the API I want elsewhere in the thread.
Yes I think I see what you mean now.
Consider using conditional fields using the @include or @skip directives
I addressed that in the original post. That requires (1) adding potentially hundreds of boolean query variables to the query (and neither Apollo nor Relay give you any affordance for populating these from context, which you'd need for descendant components to add them) and (2) the parent query still knowing and listing out every possible field that could be added to the query, but with a @skip directive on it and (3) how do you deal with fields on nested objects? Not feasible in the slightest.
This is a classic static/dynamic problem. Luckily, in the case of React, there may be a really good solution that covers most necessary use cases. First, an analogy:

Consider Ruby's method_missing and corresponding respond_to? methods [1]. method_missing may respond to a method dynamically, but respond_to? should return true if a method will be responded to. If you constrain yourself to normal methods, or carefully defined method_missing bodies, you can predict with 100% accuracy, but what if you do something like this?

    class Whoops
      def method_missing(method)
        if method == :fancy and Random.rand > 0.5
          "it's your lucky day!"
        else
          super(method)
        end
      end

      def respond_to?(method, include_private = false)
        if method == :fancy
          true # Well, actually, "maybe?".
        else
          super
        end
      end
    end
Temporarily ignoring the problem created by randomness: Implementing respond_to? methods is annoyingly redundant. In order to fully-automate the process of implementing respond_to?, you'd need to analyze a method_missing implementation. In the common case, this is static type inference. In the limit case, this is arbitrary abstract interpretation.

However, effectful code, such as this random example, is provably impossible to analyze correctly! You need to have some conservative approximation. Returning true from respond_to? for the :fancy method means that respond_to? should actually be called might_respond_to?.

OK, now back to React and GraphQL: Luckily, over-fetching data is usually not that big a deal (as long as you're within bandwidth constraints). It's often desirable to! Since you might need that data soon when the view changes. Knowning this, it's safe to assume a method for assembling a GraphQL query from fragments can be conservative. That's essentially what a totally static, manual composition of query fragments does. If that's a tight enough approximation for you (like some other commenters have implied) then great! But what if it's not?

Luckily, virtual DOMs are dramatically simpler and less powerful than arbitrary method_missing implementations. If your render method is already pure, you can call it an extra time to use it as its own abstract interpretation. It will produce a virtual DOM, which you can then analyze yourself. For example:

    let capture = (f) => {
      let oldCreateElement = React.createElement;
      try {
        React.createElement = (component, props, ...children) => {
          return {component, ...props, children};
        };
        return f();
      } finally {
        React.createElement = oldCreateElement;
      }
    };

    class DynamicQuery extends React.Component {
      graphql() {
        let dom = capture(() => this.render());
        return gatherQuery(dom);
      }
      render() {
        ...
      }
    }
Here, the gatherQuery method will walk the objects returned by our monkey-patched createElement method, composing query fragments along the way. By following the component references, you can find static methods (easy!), or even instantiate components to do recursive renders yourself (this gets hairy, since React doesn't really make any of this public API). You can do something pretty simple with this: Just hard code a few component types to make some limited dynamic decisions, fallback to static over-query beyond that. Or you can get really fancy and do recursive rendering and analysis, if you really want to.

[1] http://blog.enriquez.me/2010/2/21/dont-forget-about-respond-...

That would be a similar approach to the `getDataFromTree` function that Apollo offers: http://dev.apollodata.com/react/server-side-rendering.html#g...

But it's way more work than just passing fragments up the tree via `context` in `componentWillMount` or `componentDidMount`, which is relatively simple by comparison. :)

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

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 [1] https://github.com/motleyagency/devday-tutorials/blob/master...

Thank you, I will be checking on Postgraph and your tutorial, I'm really interested in adding GraphQL to my development stack.
> it seems that implementing a GraphQL server is not an easy task

It's actually really easy, I encourage you to look into it! I've written plenty of APIs over the years and it's one of the more pleasant experiences I've had.

I wrote this simple 35-line GraphQL server implementing a demo schema in 2 minutes just while I was replying to your comment: https://gist.github.com/exogen/d5ddf86dd7f9efd15d3fabdace759...

You make it sound pretty easy, I will check on that example for sure.
You can wrap any existing API with graphql https://medium.com/taller-team/graphql-today-using-apollo-fo...
Regarding implementing GraphQL Servers: You can also check out https://github.com/graphcool/framework (I'm one of the creators) as well as the community driven https://www.howtographql.com/
The GraphQL Framework seems cool, specially the GraphQL CRUD API. However I don't like the idea of having to use GraphQL Schema Definition Language, I want something that takes my MySql or PostgreSQL Schema and automatically generates the code to allow me to perform CRUD operations over it via a GraphQL API, and extent from it...
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.

Facebook’s Relay solves the throughput problem by effectively precompiling queries. Their production apps only make a subset of the requests of the full graphql language - and those are frozen at deploy/compile time. In some sense this makes their production graphql more like “auto-generated REST” from the server’s perspective.

That said, thinking of this in terms of client vs server devs isn’t useful - it’s not like anyone on the team want to see the servers burn. The beauty of graphql is it’s descriptiveness - you could create a query performance score and fail CI if your main page queries are hitting too high of a cost. A meet up in SF last year had some FB engineers discussing this - they do something along these lines before shipping their android/iOS apps.

> Facebook’s Relay solves the throughput problem by effectively precompiling queries. Their production apps only make a subset of the requests of the full graphql language

I am aware of this, and it seems ridiculous to me. It discards the value of having a graph query language. I'm open to being wrong about this assessment.

Why am I using a new query language if it's going to end up being exactly the same as the old rest query environment?

> That said, thinking of this in terms of client vs server devs isn’t useful

Considering it from a typical labor division standpoint or by technical dependency and requirements isn't useful? I disagree.

> The beauty of graphql is it’s descriptiveness

Which you have said the "performant" way to handle is to discard and turn into a formulaic series of bytes that is indistinguishable form a new term encoding on restful POST bodies.

> you could create a query performance score and fail CI if your main page queries are hitting too high of a cost.

This is exactly how everyone already tools RESTful endpoints. Whole industries are built around scoring endpoints this way.

Inevitably my concern about GraphQL being hard to implement server side is inevitably met with Facebook's outer and inner solution. The outer solution: Just pretend it's graphql, really it's postbodies and we won't let you write custom queries except for column filtering (which these solutions WILL fetch).

The inner solution, it has been intimated to me, is they have custom GQL libraries written in Haskell using Haxl that they don't share because the industry is too busy being afraid of it. Sadly, these don't appear to be open source (and even if they were, a lot of shops would not be equipped to use them).

> Why am I using a new query language if it's going to end up being exactly the same as the old rest query environment?

the client controls the APIs without you touching them. It is required that the client syncs up with the server but you don't have to touch backend code to change the set of fields you want (this works for internal clients, not open APIs, clearly).

If you're thinking "but I can just allow the client to select fields in my JSONAPI compliant endpoint", yes, you can, and when you add includes for nested fields, and nested selections, you basically get 90% of graphql :)

> yes, you can, and when you add includes for nested fields, and nested selections, you basically get 90% of graphql :)

True, but until someone provides a great software kit for doing that work, I'm not going to allow arbitrary extensions.

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

"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 [2]: https://developer.github.com/v4/guides/resource-limitations/

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

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

Query whitelist: Not trivial to do (properly) from scratch, but seems to be well-supported in library form.

Complexity caps: Depends on the server implementation. Sangria (Scala implementation) has it built in. For others, i'm not sure. It would be easier to add a depth cap, but a complexity cap is more useful. I think whitelisting and.or rate-limiting is the way to go if you're actually concerned about your GraphQL server being abused though.

Query whitelist: Takes the query language out of the game and makes the system indistinguishable from old pre-restful .cgi endpoints.

Complexity caps: In the absence of good query scheduling this is a defense mechanism by GQL server providers. Great. But why are we doing it in the first place. This is still more complex with nearly identical outcomes to restful endpoints.

A query white list is functionality identical to hand-written endpoints to serve each use case, except there's no hand-writing involved.

As long as you have a half-decent build pipeline and embrace static patterns on the client, you get to keep all of the benefits.

To be honest, you seem like someone who hasn't done a lot of client-side work, especially with component systems. The benefits to development speed and maintainability are significant, and nearly all the downsides can be mitigated.

If you're only approaching this from the perspective of a backend developer, i'd fully expect GraphQL to be underwhelming to you.

> 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 capsi

I'm sorry, but I categorically reject the value of a query language for APIs if you turn it into something indistinguishable from a POST-body driven web framework with less cachability.

What is the point of a query language that may act across multiple sub-domains if your provider then says, "But only these queries are allowed."

AFAICT, it's a waste of time. You lose so much in the migration from a RESTful postbody API in exchange for what amounts to... well... a DSL for writing API calls that any javascripter worth their salt could have made already.

You do realize that "relatively optimal" is an oxymoron?
It is no more an oxymoron than "locally optimal". There is always a scope for claims of optimal in practice.

Perhaps it was "superfluous" to say. But then, so was your post.

No I don't agree.

To me, it's like saying "somewhat unique".

The phrase "relatively optimal" doesn't make sense to me. Do you have a server that's optimal relative to some other server? That's just "optimal", no? And if it's sub-optimal relative to some other server, that's just "sub-optimal"?

Locally optimal is a known thing (it even has it's own wikipedia entry https://en.wikipedia.org/wiki/Local_optimum), but "relatively optimal" is just sloppy thinking in my view.

If you want to know what I meant, I can tell you.

In practice I was able to have a GraphQL server which produced optimal downstream request waterfalls (i.e. to internal APIs) for every query that our app made in practice, and nearly every contrived query I could come up with.

So what I mean is that if you'd hand-written the code to handle those data requirements, in nearly all scenarios you wouldn't have been able to do better.

Does "optimal, nearly all the time" make you feel better?

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?
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.
Apollo supports batching queries at the transport layer. graphql-ruby and I'm sure other graphql servers support this out of the box, which means with about ~10 LOC changed you can have your cake (separate queries for independent update) and eat it (combined into one initial call).
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.

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?

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) 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.
Usually GraphQL APIs are served alongside a GraphiQL client, which lets you run queries, and read the schema and docs for that API. See, for example, the Star Wars API example: http://graphql.org/swapi-graphql/
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
This is why Om Next (in cljs) with datomic backend is so powerful!
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.

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.
It is, but if you are going that route and have not chosen a client side framework then Om Next is a better fit on the client for this type of setup.
om.next does a similar thing
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.

Machine learning is not pixie dust.
This has become one of my pet peeves over time. After the recent round of ML/DL/AI hype train it appears that a number of people have adopted this belief that these technologies are pixie dust you can sprinkle over any arbitrary problem and knowledge will be automatically synthesised out of nothing.

Almost like an excuse to avoid having to think about a problem. "But what about ..?" "A DEEP LEARNING AI NEURAL NETWORK WOULD TOTALLY SOLVE IT RIGHT?!"

But what if you added some ML to extract common UX patterns to it hmm?
Sounds like the perfect place to use a cryptocurrency as a distributed query ledger.