Should GraphQL allow Recursive Fragments?
Examining the proposal on allowing recursions in fragments leads me to ask some fundamental questions, including whether the GraphQL spec should allow recursive fragments. This article explores the pros and cons and, prompted by the request for feedback from the GraphQL Working Group, suggests a few good use cases for them.
Doing it wrong vs. doing it right
As with every piece of technology, allowing recursions in fragments is all by itself neither good nor bad — a lot depends on how we use it.
For instance, this is how to use the feature the wrong way, producing a never-ending execution cycle:
fragment CommentData on Comment {
content
...CommentData
}
In the query above, the recursive fragment CommentData
is looping over a single Comment
entity so that, if the GraphQL server is not able to detect the cycle, it will loop forever.
In this example though we're doing it right:
fragment CommentData on Comment {
content
responses {
...CommentData
}
}
In the query above, the recursion of CommentData
is applied on a different Comment
entity, which is a response to the original one. Because a chain of comment responses cannot form a cycle, this query will halt in a finite number of steps (as many as the longest chain of responses in the comment thread).
Hence, depending on which entity they are applied to, recursions in fragments can go both ways, producing a never-ending loop or a sequence that is guaranteed to end.
Static analysis can't tell right from wrong
The developer can comprehend that applying a recursive fragment on CommentData
will not form loops. This understanding derives from knowing that a comment thread will never form a cycle, so that the field responses
will never contain the same entity that is currently being iterated upon (or one further above the comment thread), and that's why the execution of the query will necessarily end.
When performing static analysis, though, this is not so straightforward to assert, because the tool would need to have a complete understanding of the behavior of all entities in the schema, which is difficult to provide.
For instance, this other query has exactly the same shape as the previous one, however it could very well create a never-ending loop:
fragment FriendData on Friend {
name
friends {
...FriendData
}
}
If the Friend
information in the system is bidirectional, so that entity "Abbot"
knows that it's friend with "Costello"
, and "Costello"
knows that it's friend with "Abbot"
, then the following cycle could be formed, and the execution may very well never end:
{
"data": {
"person": {
"name": "Abbot",
"friends": [
{
"name": "Costello",
"friends": [
{
"name": "Abbot",
"friends": [
{
"name": "Costello",
"friends": [
{
"name": "Abbot",
"friends": [
{
"name": "Costello",
"friends": [
{
...
}
]
}
]
}
]
}
]
}
]
}
]
}
}
}
This is why the spec currently bans recursions in fragments: if there's no way to tell right from wrong using static analysis so that the process could be foolproof and automated, then we need to ban it all over the board as to have peace of mind that things cannot go awry.
How we could use recursive fragments
With no guarantee that they will not iterate forever, it's understandable for the GraphQL spec to ban recursive fragments. However, because developers can in certain circumstances be confident that their queries will not loop, they should be given the chance to allow recursive fragments.
As I proposed in Should GraphQL Be Different for Different Users?, a possibility would be for the GraphQL spec to ban recursive fragments by default, but to introduce feature-flags to allow the admin of the API to disable this validation. Alternatively, instead of the GraphQL spec, this behavior could be provided by the GraphQL server.
Use cases for recursive fragments
The GraphQL working group has requested feedback so taht it may reconsider allowing recursions in fragments if there are good use cases for it.
. . . rather than this being just a removal, what do we need to add to the spec to reach the safety that we have today? Can you raise more use cases for it to make it more convincing?
So here I'll propose a few use cases that could benefit from recursive fragments.
Introspection query
The most evident use case is the introspection query executed by GraphiQL and other tools to obtain the full information from the schema.
The introspection query contains this fragment:
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
Because there is only a maximum depth that types in the schema can be related to each other (evidenced by ofType
, which appears 7 times), and no circular references can be created, then this fragment could be replaced using a recursive fragment and it would be certain to halt:
fragment TypeRef on __Type {
kind
name
ofType {
...TypeRef
}
}
Comments
As mentioned above, a comment thread cannot form cycles, hence the following query is certain to halt:
query GetPostComments($postID: ID!) {
post(id: $postID) {
comments {
...CommentData
}
}
}
fragment CommentData on Comment {
id
content
date
author {
name
url
}
responses {
...CommentData
}
}
Menus
A menu from an application is normally composed of a first level that contains links and sublevels, which also contain links and further sublevels, and so on until there are no more items to display in the menu.
Similar to comments, the nested sublevels in the menu cannot form cycles; therefore this query is certain to halt:
query GetMenu($menuID: ID!) {
menu(id: $menuID) {
items {
...MenuItemProps
}
}
}
fragment MenuItemProps on MenuItem {
id
label
url
children {
...MenuItemProps
}
}
Blocks
Content Management Systems, cloud-based services, and websites are increasingly using "blocks" (a high-level component that has a definitive purpose) for providing functionality to their users. This trend is backed by WordPress, which has recently released the Full Site Editor to rely on blocks for creating the layouts for the site.
As content created in WordPress can be accessed via an API, frameworks for creating sites (such as Gatsby or Next.js) can use WordPress as a data source. These frameworks can then read and extract the properties defined within the blocks created in WordPress, and display them in the site using a different layout.
Blocks can be complex structures, containing other blocks (such as a block "Image Cover"
containing nested blocks "Image"
and "Header"
). This structure is unidirectional: a block can contain nested blocks, but these cannot contain their parent. Therefore using recursive fragments to extract their data is certain to halt:
query GetPostBlocks($postID: ID!) {
post(id: $postID) {
blocks {
...BlockData
}
}
}
fragment BlockData on Block {
id
content
properties
nestedBlocks {
...BlockData
}
}
Conclusion
In my opinion, there are good reasons why recursive fragments might be banned, but these are not 100% compelling. As there are valid use cases for recursive fragments, and in many circumstances we can be completely certain that there will be no adverse consequences to using them, then API admins should be given the ability to use them.
The working group is asking for feedback so everyone is welcome to contribute an opinion on this topic.