Declarative strict ordering on links via GraphQL

Hello community!
I’ve been using Dgraph at my company to prototype our product, but we’re looking to keep in production at launch. The Dgraph database is an absolutely remarkable piece of technology. This is my first post to the community so I apologize if this topic is malformed in any way.

Our question is: using the generated “updateType” mutations, can we reorder the links arbitrarily without adding additional properties? And if not, can that be a feature of Dgraph in future?

Experience Report for Feature Request

For (IP related) reasons, I’ve translated the problem we faced into the Todo App example found in the docs.

As such, consider the following Todo App data for this topic:

{
  "data": {
    "addUser": {
      "user": [
        {
          "username": "alice@dgraph.io",
          "name": "Alice",
          "tasks": [
            {
              "id": "0x2",
              "title": "Avoid crowd"
            },
            {
              "id": "0x3",
              "title": "Wash your hands often"
            },
            {
              "id": "0x5",
              "title": "Avoid touching your face"
            },
            {
              "id": "0x6",
              "title": "Stay safe"
            }
          ]
        }
      ]
    }
  },
  "extensions": {
    "touched_uids": 41
  }
}

What you wanted to do

We want to reorder the tasks using the generated updateUser mutation like so:

mutation {
  updateUser(input: {
    filter: { username: { eq: "alice@dgraph.io" }}
    set: {
      tasks: [
        {
          id: "0x6"
        },
        {
          id: "0x2"
        },
        {
          id: "0x3"
        },
        {
          id: "0x5"
        }
      ]
    }
  }) {
    user {
      tasks {
        id
        title
      }
    }
  }
}

so that “Stay Safe” was at the top of Alice’s list of tasks. We want her to stay safe.

The mutation, however, returns the result:

{
  "data": {
    "updateUser": {
      "user": [
        {
          "tasks": [
            {
              "id": "0x2",
              "title": "Avoid crowd"
            },
            {
              "id": "0x3",
              "title": "Wash your hands often"
            },
            {
              "id": "0x5",
              "title": "Avoid touching your face"
            },
            {
              "id": "0x6",
              "title": "Stay safe"
            }
          ]
        }
      ]
    }
  },
  "extensions": {
    "touched_uids": 51
  }
}

…preserving the original order of the links, meaning we could not reorder Alice’s tasks.

What you actually did

We added a “priority” field to the Task type, mutated accordingly, and ordered the result in our query like so:

mutation {
  updateUser(input: {
    filter: { username: { eq: "alice@dgraph.io" }}
    set: {
      tasks: [
        {
          id: "0x6",
          priority: 2
        },
        {
          id: "0x2",
          priority: 1
        },
        {
          id: "0x3",
          priority: 1
          
        },
        {
          id: "0x5",
          priority: 1
        }
      ]
    }
  }) {
    user {
      tasks(order: { desc: priority}) {
        id
        title
      }
    }
  }
}

which does return “Stay Safe” as the first task item.

Why that wasn’t great, with examples

Adding the extra field complicated the API, and thus, complicated our queries. We also need to ensure that each “priority” value is unique so that the ordering remains strict. For us, this means we must use an initial query to return the current highest priority in order to move tasks to the top of the list.

Any external references to support your case

The GraphQL spec defines the List type as an ordered collection; stating “GraphQL servers must return an ordered list as the result of a list type” https://spec.graphql.org/June2018/#sec-Type-System.List

Obviously, Dgraph does indeed do this. However, one could argue that the links themselves under the GraphQL interface should also be ordered accordingly.

To quickly address this, ordering via a priority score, or a timestamp, etc. is the right thing to do. Even though it might sound like a workaround, it allows you to later order things in various ways, depending upon the context.

References to look up:

Thanks Anthony,

I see this has already been discussed. My mistake for not finding any of those sooner…

I’d imagine that for 90% of use-cases, the Set-like behavior of Lists would work absolutely fine. However, the power of GraphQL comes from the brevity of the APIs that utilize it. If I may address the workarounds mentioned in this topic and others.

Doing this requires an additional query to find all current scores. For a list of size n, inserting at position i may require n-i mutations (in the case where the pre-insertion orders respectively are [1,2,3,4, … , n-1, n]). This places the generated GraphQL API into a performance pitfall similar to the N+1 Problem; a problem GraphQL aims to solve

This is actually a pretty fair idea. Unfortunately this doesn’t work well for storing lists of complex structures, such as large JSON-like objects. At best, we’ll end up overfetching everything in the list, and at worst, the objects will refer to the IDs of other types, leading to additional queries. Again, the very issues GraphQL aims to solve in the first place.

Just to be clear, I understand the reasoning behind the current behavior, and the performance implications of changing said behavior. Personally (and I believe others will share this opinion), I think an “@orderable” directive on lists is a reasonable feature. Under the hood, it could use an “order” value that isn’t seen in the generated API. Another point made in other topics is that the GraphQL spec doesn’t say that the lists should be orderable, just they are ordered. So yes, Dgraph lists are to spec in that sense, but for the reasons outlined above I believe it’s against the spirit of GraphQL for Dgraph to be missing such an option.

1 Like