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 Bindings with Fragments for a Stitched Schema #70

Closed
LawJolla opened this issue Mar 22, 2018 · 10 comments
Closed

Using Bindings with Fragments for a Stitched Schema #70

LawJolla opened this issue Mar 22, 2018 · 10 comments
Assignees

Comments

@LawJolla
Copy link

LawJolla commented Mar 22, 2018

My questions are how to use fragments with bindings and how to use Dataloader with bindings.

To preface, I may be completely lost in my own head on this problem. So let me give the 100,000ft view and hopefully there's an answer.

I have two servers, one for vehicles and one for files. The vehicles server needs to stitch in the file server. The vehicle server wants to query:

{
   vehicles {
         year
         make
         ....
         heroImages {
               url
         }
}

heroImages comes from the file server. Here's how I've implemented this so far.

I used get-schema to save a copy of the file server to the vehicle server. I imported the needed types through schema.graphql.

My app schema:

  const appSchema = makeExecutableSchema({
    typeDefs: [
      importSchema(`./src/schema.graphql`),
      `extend type Vehicle { heroImages: [FileGroup!]! }`
    ],
    resolvers
  })

Then I build the binding for the file server

const makeFileServiceLink = new HttpLink({
  uri: `url`,
  fetch
})

const remoteSchema = makeRemoteExecutableSchema({
    schema: await introspectSchema(makeFileServiceLink),
    link: makeFileServiceLink
})

const fileServer = new Binding({
    schema: remoteSchema,
    fragmentReplacements: {}
})

All good so far. But here's where things go sideways. Obviously heroImages is an N+1 call. So I made a Dataloader...

  const heroImagesLoader = new DataLoader(async ids => {
    const images = await fileServer.query.vehicleHeroImages(
      { input: { vehicleIds: ids } },
    )
    return ids.map(id => {
      const FileGroup = images.find(({ groupId }) => groupId === id)
      return FileGroup ? [FileGroup] : []
    })
  })

And the resolver...

{
  Vehicle: {
     heroImages: {
        fragment: `fragment VehicleFrag on Vehicle { stockNo }`,
        resolve: async ({ stockNo }, source, ctx, info) => {
          const { heroImagesLoader } = ctx.loaders
           return heroImagesLoader.load(stockNo)
       }
    }
  }
}

The first few problems rear their heads.

  1. How to get info into the binding query. DataLoader's API is pretty strict. I've made it work by turning it into an object, but whoa hack...
    heroImagesLoader.load({ id: stockNo, info}) and then mapping the ids and passing ids[0].info for the info binding parameter.

  2. How to get a field into the binding if it's not queried. In my set up, the vehicle server stores a stockNo for each vehicle. The file server groups files by a groupId. For vehicles, the stockNo is the groupId. Therefore, I cannot map the results back without knowing the groupId field. I'm sure there's a hack I could do to info, but isn't there a better way?

I tried messing with the binding's fragmentReplacement to no avail. e.g.

 const fragmentReplacements = extractFragmentReplacements({
    Query: {
      vehicleHeroImages: {
        fragment: `fragment GroupId on FileGroup { groupId }`
      }
    }
  })

 const imageServer = new Binding({
    schema: remoteSchema,
    fragmentReplacements
  })

to no avail.

Any help is a huge help! Thanks everyone as always for your time and effort!

@marktani
Copy link

Thanks a lot for your detailed description, @LawJolla.

I am curious to hear the perspective of @kbrandwijk, @schickling and @freiksenet on this topic 🙂

@maxtechera
Copy link

maxtechera commented Mar 28, 2018

I'm currently working on a very similar user case. I would like to know how to control which fields get requested when the function is used without info and some doc about fragmentReplacements.
If you can point me in the right direction I might be able to add later to docs.

@schickling
Copy link
Contributor

Thanks a lot describing your use case @LawJolla (and apologies for the delayed response). We'll be looking into this over the following days. Can you please provide a minimal repo to reproduce your scenario?

@LawJolla
Copy link
Author

LawJolla commented Apr 2, 2018

Thanks @schickling !

Here's the minimal repo. (I kept my development / shared endpoints in the code to make it faster on everyone)

https://github.com/LawJolla/graphql-stitched-bindings-example

@schickling
Copy link
Contributor

Thanks a lot @LawJolla. @timsuchanek will look into this later this week! :)

@timsuchanek
Copy link
Contributor

Thanks for the example repo @LawJolla ! One question: Here you're providing a separate schema definition for the stitching. Is there a particular reason to not put this into the schema.graphql? https://github.com/LawJolla/graphql-stitched-bindings-example/blob/master/mainServer/src/index.js#L76

We're now working on a utility function that will help with your use case

addFragmentToInfo(info: GraphQLResolverInfo, fragment: string): GraphQLResolverInfo

That solves your issue like this:

addFragmentToInfo(idsAndInfo[0].info, 'fragment EnsurePostId on Image { postId }'),

@LawJolla
Copy link
Author

LawJolla commented Apr 6, 2018

Thanks @timsuchanek , that would be perfect!

I've tried putting the extend type definition into the schema, but I've always had it kicked back. Maybe I was just doing it wrong. I'll give it a try.

But no, there's no particular reason it's there other than I couldn't get it to work in the schema and Graphql-tools shows that pattern in their old examples.

@schickling
Copy link
Contributor

schickling commented Apr 7, 2018

Re extend type: If this turns out to be a bug in graphql-import, please open an issue! This should be a lot simpler this way!

@LawJolla addFragmentToInfo was merged & released yesterday. Want to give it a shot?

@LawJolla
Copy link
Author

LawJolla commented Apr 9, 2018

Awesome, thanks @schickling and @timsuchanek! I should be able to give it a spin today.

@LawJolla
Copy link
Author

LawJolla commented May 1, 2018

Thanks for your help and I'm sorry for the long delay. I ran into errors and then had other business crap come up.

I finally got back to it and it works great. The only thing that's confusing to me is I wanted to use it as..

const info = addFragmentToInfo(...)
await db.query.type(..., info)

But once I changed it to

await db.query.type(..., addFragmentToInfo(...))

It worked perfectly. Thanks for the help!!

@LawJolla LawJolla closed this as completed May 1, 2018
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

5 participants