Should the opaque cursors in connections be stable

2019-05-12 09:08发布

问题:

The RANGE_ADD mutation requires an edgeName so that it can insert the new edge into the client side connection. As part of its query, it also includes the cursor.

The issue is that the server has no way of knowing which args the client might be applying to a connection when it's generating the edge response.

Does this mean that the cursor should be stable?

回答1:

In general, cursors are not required to be the same when connections are used with different arguments. For example, if I did:

{
  namedFriends: friends(orderby:NAME first:5) {
    edges { cursor, node { id } }
  }
  favoriteFriends: friends(orderby:FAVORITE first:5) {
    edges { cursor, node { id } }
  }
}

Different backends might be use to server those two connections, since we might have different backends for the two orderings; because of that, the cursors might be different for the same friend, because they might need to encode different information for the different backends.

This makes it tricky when performing a mutation, though:

mutation M {
  addFriend($input) {
    newFriendsEdge {
      { cursor, node { id } } // Which cursor is this?
    }
  }
}

In cases like this, where the mutation is going to return an edge from a connection, it's useful for the field to accept the same non-pagination arguments that the connection does. So in the above case, we would do:

mutation M {
  addFriend($input) {
    newNamedFriendsEdge: newFriendsEdge(orderby:NAME) {
      { cursor, node { id } } // Cursor for namedFriends
    }
    newFavoriteFriendsEdge: newFriendsEdge(orderby:FAVORITE) {
      { cursor, node { id } } // Cursor for favoriteFriends
    }
  }
}

And ideally, the implementation for newFriendsEdge(orderby:FAVORITE) and favoriteFriends: friends(orderby:FAVORITE first:5) share common code to generate cursors.

Note that while the cursors are not required to be the same, it's fine if they are, as an implementation detail of the server. Often, the cursor is just the ID of the node, which is a common way for this to happen. In practice, in these situations, if a argument on the connections doesn't affect the cursor, we would omit it from the mutation's edge field; so if orderby didn't affect the cursor, then:

mutation M {
  addFriend($input) {
    newFriendsEdge {
      { cursor, node { id } } // orderby didn't exist on newFriendsEdge, so this cursor must apply to both.
    }
  }
}

This is the common pattern in our mutations. Let me know if you run into any issues; we thought through the "arguments change cursors" case when developing the pattern of returning edges on mutations to make sure there was a possible solution to it (which is when we came up with the arguments on edge fields idea), but it hasn't come up in practice all that much, so if you run into trickiness definitely let me know, and we can and should revisit these assumptions / requirements!