Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using resolved fields as input argument variables #88

Closed
HurricaneJames opened this issue Aug 25, 2015 · 10 comments
Closed

Using resolved fields as input argument variables #88

HurricaneJames opened this issue Aug 25, 2015 · 10 comments

Comments

@HurricaneJames
Copy link

I'm fairly certain this is not currently possible, and I'm not even sure it is desirable. That said, is there some way to specify an argument variable based on previously fetched data?

For example, lets assume we have a contentItem type. That contentItem has two fields, isLive and liveData. liveData includes a bunch of information about who set the contentItem to be live and when. We want that information, but only if the contentItem is actually live.

query content {
  contentItem {
    isLive
    liveData @include(if: $isLive) {
      liveTime
      user {
        username
      }
    }
  }
}

If it is not possible, is this a feature worth considering? I see three big downsides. First, it adds some complexity to the resolution of fields. Second, we lose some parallel execution in the query, though not all. Third, there are many use cases for such a variable that are really bad design.

I am going to admit that the original reason I wrote this issue was one of those mildly bad design decisions. Fortunately, I realized it was really bad design as I start writing it down. However, the isLive/liveData example stuck with me as useful. Granted, right now it would just return undefined or null, which is probably just as good. Still, something to think about.

@jtmarmon
Copy link

+1

Use case: mutations based on prior payloads.

For example, say we want to create some content for a user after they register based on preferences they give us:

mutation M {
   createUser(firstName: 'bob') { id }
   createPreferences(user_id: #createUser.id, preferences: "doesn't appreciate purple") {
       success
    }
}

I don't think there's a way to achieve the above without two requests at the moment. What do you think @leebyron

@dylanahsmith
Copy link
Contributor

However, the isLive/liveData example stuck with me as useful. Granted, right now it would just return undefined or null, which is probably just as good

Yes, that isn't really a concrete example of an actual problem.

I can kind of see where you are going with that though, since you might just need one field, but need a fallback to other fields if it doesn't exist. For example, if a user hasn't uploaded their photo, you might want to show a stock picture that is derived based on their country, initials, and/or some other field.

If you are using your own API, the solution there is probably to let the server compute the fallback, which could be requested through another field or argument to specify that you want a fallback image. That way you can simplify the client code, reduce the response size and provide consistency across client apps.

The proposed feature might be more useful for a third-party apps which don't have control over the API, although there will probably always be more complex conditions in the client side code that won't be representable in GraphQL to capture the differences in data requirements between those code paths.

@jtmarmon what if you move your mutations on some other object out of the top-level fields on the mutation root. E.g. normally you could use a user field on the mutation root of type UserMutation in order to create preferences as follows:

mutation M($userId: ID) {
  user(id: $userId) {
    createPreferences(preferences: "doesn't appreciate purple") {
      success
    }
  }
}

then you just need to expose that UserMutation on the payload of the createUser mutation, so the GraphQL for your two mutations might look like

mutation M {
  createUser(firstName: 'bob') {
    user {
      id
    }
    mutation {
      createPreferences(preferences: "doesn't appreciate purple") {
        success
      }
    }
  }
}

that way you are basically using GraphQL in an object oriented way, similar to method chaining in a programming language.

@freiksenet
Copy link

You can just write one mutation createUserWithPreferences. I think we might be opening a can of worms if we add non-pure operations inside GraphQL body, there are questions of, eg, ordering.

As for original questions, one way to do it would be to create an interface with two implementations. One will include liveData, another will not. Then you could query them as following:

query content {
  contentItem {
    isLive
    ... onLive {
      liveData {
        liveTime
        user {
          username
        }
      }
    }
  }
}

@dylanahsmith
Copy link
Contributor

I think we might be opening a can of worms if we add non-pure operations inside GraphQL body, there are questions of, e.g. ordering.

The ordering is specified as being serial for mutations, including for the sub-selections: http://facebook.github.io/graphql/#sec-Serial-execution

@freiksenet
Copy link

It's only serial on the top level, subselections are executed in parallel.

There are many other questions - can you execute mutations only inside other mutations? What is the type of mutations? Do mutation payloads need to include what mutations are going to be possible inside them? Also what is going to be the result of the above query?

@dylanahsmith
Copy link
Contributor

Oh, I see. It is using "normally" to mean unordered. The previous section explains that clearer and also says that non-top level mutation fields must be side-effect free.

So yes, it does look like a createUserWithPreferences would be needed to do that right now, but that might be heading towards designing the schema for a specific client, which GraphQL allows us to avoid in most cases.

If we wanted to support that type of non-top level mutation, then we would want to distinguish between a mutation object type and a query object type to specify that selections in a mutation object type are executed serially.

@freiksenet
Copy link

You could create a mutation that knows how to handle any kind of related object, but I realize that it's a lot of work.

@He-Pin
Copy link

He-Pin commented Sep 19, 2016

We at taobao have implemented it in this way and it works great.

@leebyron
Copy link
Collaborator

leebyron commented Dec 7, 2017

Closing this aging issue

@leebyron leebyron closed this as completed Dec 7, 2017
@japrogramer
Copy link

We at taobao have implemented it in this way and it works great.

In what way are you referring to?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants