I have doubts about how to implement a GraphQL API while maintaining some abstractions.
[*] = recommended reading
[**] = required reading
The GraphQL context
In GraphQL you have nodes which you can traverse to get to list of nodes or to other single nodes.
The idea is that with a query like:
query { viewer { id, fullName, authoredPosts { id, title, body, comments { id, text } } } }
You are starting from the viewer
node (which in the example identifies the person that is logged in) and you are fetching some of its data, then traversing to the list of authored posts and for each of those fetching data and the comments.
Now, on the backend you receive that query and it gets parsed by the library you are using and it calls resolvers that you write. Resolvers are lazily evaluated for each field so that, in the example, if you don’t ask for authoredPosts
they don’t get computed.
The recommended “right way” [*]
In the ASP.NET world you would have a database (say Postgres) which stores the necessary data. Then you would have a “Repository” class for each element (users, posts and comments) which exposes ways to retrieve data and modify data. Then you would have a “Service” class for each of those which takes the corresponding repository classes and adds business logic and exposes only the methods that need to be exposed. And finally the resolvers would use the service classes.
You basically separate the logic in few layers:
- Database
- Repositories
- Services
- Resolvers
The problems [*]
With the described approach you have few issues:
- Certain queries like “get me all posts that satisfy X and their comments which satisfy Y” can be expressed in a single query (for efficiency reasons) but you can’t with the above architecture because repositories separate posts and comments early on.
- Sometimes the GraphQL query and the query that could be performed (with appropriate authorization and validation checks) could be resolved in a single graph database query (assuming you decide to use something like Neo4j as opposed to Postgres) and repositories and services block yourself in that case.
Question [*] [**]
How can you avoid long and complex resolvers by splitting authorization/validation/logic responsibilities appropriately while also not sacrificing efficiency this much?
This is not premature optimization
The described issues are performance issues so many might jump on this train of yelling at me that this is premature optimization and “better make it work first and then make it efficient), but:
- Decisions on how to manage resolvers and dependencies might require a whole different architecture.
- As far as I know in the GraphQL world efficient queries is the #1 problem due to the fact that calculating the complexity of each possibly query is almost impossible and malicious attacks are facilitated in this by being able to create computational heavy queries with a single request. Libraries usually allow the user to set a limit on the depth of the query but that might be reasonable only if you are pretty sure that fetching all comments of all posts of a user doesn’t fall into the N + 1 problem.
- One of the main strengths of GraphQL is exactly the fact that you can optimize these kind of queries on the server side.