6 ms·
Hey, thanks for your reply. I think you guys have done an amazing job creating a very powerful GraphQL client. However, to me "smart" GraphQL clients don't make
by jensneuse 6y ago
Hey, thanks for your reply. I think you guys have done an amazing job creating a very powerful GraphQL client. However, to me "smart" GraphQL clients don't make much sense. My approach with WunderGraph is the following: You write down all your Operations using GraphiQL. We automatically persist them on the server (WunderNode) and generate a "dumb" typesafe client. This client feels like GraphQL and behaves like GraphQL but is not using GraphQL at all. It's just RPC. This makes using GraphQL more performant and secure at the same time. Additionally it's a lot less code and a smaller client because those RPC's are a lot simpler.
- rattray 6y agoThat sounds really cool! But I don't think it addresses why I'd want a "smart" GraphQL client: normalized caching on the client. Say I have a dashboard where multiple panels on a given page make their own requests, since the panels are shared between many pages. But they share some objects. If I get updated data in a request from one panel, I'd like to see that update in all panels, without triggering more requests. Side note that a magic layer to have each of those components combine their requests into one would actually hurt performance, since it's better to load the requests in parallel. And manually merging them into one would be quite a chore.
- jensneuse 6y agoClient side caching with a normalized cache implementation is very hard to get right. I see why you would want that feature and if it were simple to implement I'd always want to use it. However I think we can get away with a solution that is a lot simpler than normalized caching. With persisted Queries we can apply the "stale while revalidate" pattern. This means we can invalidate each individual page and would have to re-fetch each page in case something updated. This is some overhead but the user experience is still very good. Normalized caching in the client can get super hairy with nested data. In addition, normalized caching adds a lot of business logic to the client which makes it hard to understand the actual source of truth. From a mental model it's a lot simpler if the server dictates the state and the client doesn't override it. If you allow the client to have a normalized cache the source of truth is shared between client and server. This might lead to bugs and generally makes the code more complicated than it needs to be. Is it really that bad to re-fetch a table? I guess most of the time it's not. I've written a blog post on the topic if you want to expand on it further: https://wundergraph.com/blog/2020/09/11/the-case-against-normalized-caching-in-graphql https://wundergraph.com/blog/2020/09/11/the-case-against-nor...
- rattray 6y ago> Client side caching with a normalized cache implementation is very hard to get right Absolutely true! When I worked on this at $prevCo, it was tricky and sometimes caused bugs, unexpected behavior, and confused colleagues. I will say that a proper implementation of a normalized cache on the client must have an ~inherent (thus generated) understanding of the object graph. It also must provide simple control of "whether to cache" at each request. Most of the problems we experienced were a result of the first constraint not being fully satisfied. My impression is that Apollo does a good job on both of these but I haven't used it so I can't say. I'll also note that the approach of "when one component makes an update, tell all other components on the page to refetch" sounds like a recipe for problems too – excess server/db load, unrelated data changing in front of users' eyes (and weird hacks to prevent this), etc. Of course, with the wundergraph architecture, it sounds like answer to these questions would simply be to load a given table only once per page – which means no more defining queries on the "panel" components in the dashboard, for example. All tricky tradeoffs! The right answer depends on what you're building. The Wundergraph approach sounds pretty cool for a lot of cases!
- ex3ndr 6y agoNot really, implementation of graphql cache is like day or two work.
- trevyn 6y agoFor many use cases, adding an avoidable server round-trip between a user interaction and a view update is an absolute non-starter. Milliseconds matter. Does it lead to greater complexity somewhere, and all the issues around making that complexity bulletproof? Sure. But the user experience is so viscerally different that some will demand it. I think it’s admirable to work on getting that complexity correct and properly abstracted so that it can be re-used easily.
- jensneuse 6y agoYou can avoid this problem by using Etags, stale while revalidate pattern as well as prefetching. This keeps the architecture simple without any major drawbacks.
- philplckthun 6y agoThat sounds interesting! But isn’t this like persisted queries (the Relay kind and not Automatic kind) without the benefit of prototyping your queries as a front end dev as you’re working on the front end? I’d say that’s completely fair still, just wondering. I’d also say I understand the carefulness and stance on “smart clients,” i.e. normalized caching, which is why this isn’t a default in urql, but without it I think the discussion here is much more nuanced. It’s so to speak much easier to rely on an argument with a smarter client and the Apollo ecosystem, than the rest. Anyway, I like your approach with Wundergraph so I’ll definitely check it out!
- jensneuse 6y agoI was asking myself an important question. When you write a Query, what activity are you actually currently involved in? You try to understand the API and want to query it. What's the easiest way to understand an API? Read the documentation? Where is the documentation? It's the schema, hence GraphiQL/Playground. So why would you want to switch back and forth between Documentation and Code when you want to understand an API? On the other hand, if you already use GraphiQL in your workflow, how does this look like? You write a Query in GraphiQL, then copy paste it into your code. Now if you want to add something else you go back to GraphiQL, search for another field and copy paste again. Compare that to WunderGraph: You keep getting back to GrapiQL and extend your Queries. You hit save and the code-generator re-generates the client. You don't even have to change the code if you just extended a query. The function call in the frontend simply returns more data. I wrote a feature page about this: https://wundergraph.com/features/generated_clients https://wundergraph.com/features/generated_clients I'd really appreciate your feedback on it!
- lukeramsden 6y agoIt all seems very interesting, I may try and experiment putting it in front of my current prod GraphQL schema and making a few queries, once I get the auth stuff figured out. One question though, is any of this going to be open source? The on-prem-first focus you have is certainly a selling point for me as I already run my entire backend in Amazon's ECS so adding another service for the wundergraph would be very simple - however, I'm always weary of using non-open source software that I can't fork and patch, as I've had to do that many a time due to not being able to wait for patches to be upstreamed. Regardless, I think the points you make in your blog posts are spot on, and I'm looking forward to watching this project evolve.
- sradman 6y agoAside: why is there not an RSS feed for the WunderGraph blog? I think Jens Neuse is making two important observations about GraphQL: 1. GraphQL's single URL/endpoint [1] is possibly an anti-pattern 2. ETags are important for Cache-Control and Concurrency-Control on REST endpoints The concept of prepared statements is useful for my SQL-centric brain. WunderGraph effectively creates a REST endpoint for each prepared statement (GraphQL DML). Like prepared statements in SQL, WunderGraph uses query metadata to determine the types of input parameters and the shape of the JSON response. Kyle Schrade makes an important point about canonical GraphQL queries: response payloads can be reduced by filtering JSON fields, similar to SQL projection (i.e. the columns specified in the SELECT clause). It seems that WunderGraph can potentially support both approaches by allowing optional GraphQL queries on each REST endpoint that can be used to filter the endpoint specific JSON response. [1] https://graphql.org/learn/serving-over-http/#uris-routes https://graphql.org/learn/serving-over-http/#uris-routes
- jensneuse 6y agoI don't see a problem allowing a generic GraphQL handler. It's just that I don't like the approach of allowing arbitrary queries from clients you cannot control. If this use case has a lot of demand I don't think I wouldn't support it. I'd just rather implement a seamless developer experience for code generation so you don't really want to not use it and lose the benefits.
- sradman 6y agoI think we are on the same page. From my perspective, arbitrary queries are a vector for a Denial of Service event (both intentional and accidental). This has long been one of the use cases for Stored Procedures in SQL; restrict the public interface to guard against expensive queries (large scans and sorts). Faceted Search [1] may be a counter-example but I suspect that these interfaces are implemented at least partially with Full Text Search indexes rather than purely dynamic GraphQL/SQL. It might be a useful exercise to prototype an online shopping site using WunderGraph. [1] https://en.wikipedia.org/wiki/Faceted_search https://en.wikipedia.org/wiki/Faceted_search
- zaiste 6y agoI didn't know WunderGraph, but this sounds similar to OneGraph [1], i.e. you write your GraphQL query, identify its input if needed, then persist (once) the query on the server. This returns a unique query ID that can be used to execute that query server side. In OneGraph, you can use just HTTP for that, no need for a GraphQL client library. You can use any HTTP client to trigger a POST request with the persisted query ID and its input params in the request body. This way it seems a bit easier and simpler that your approach with RPC in WunderGraph. I need to read your docs to have a full picture though. ;) [1]: https://www.onegraph.com https://www.onegraph.com