AEB-labs/graphql-weaver

One-to-many links

Closed this issue · 7 comments

I'm currently putting together an example but I'm unsure how graphql-weaver works.

  const schema = await weaveSchemas({
    endpoints: [
      {
        namespace: 'properties',
        typePrefix: 'Property',
        url: 'https://v7l45qkw3.lp.gql.zone/graphql',
        fieldMetadata: {
          'PropertyProperty.booking': {
            link: {
              field: 'bookings.bookingsByPropertyId', // field Song in namespace library
              argument: 'propertyId', // argument of library.Song
              batchMode: true,
              keyField: 'propertyId'
            }
          },
          'Query.properties.properties': {
            join: {
              linkField: 'bookings'
            }
          }
        }
      },
      {
        namespace: 'bookings',
        typePrefix: 'Booking',
        url: 'https://41p4j4309.lp.gql.zone/graphql'
      },
      {
        namespace: 'weather',
        typePrefix: 'Weather',
        url: 'https://5rrx10z19.lp.gql.zone/graphql'
      }
    ]
  });

would love some help fleshing this out.

Are you trying to do a one-to-many link between a Property and its Bookings? Looking up the related bookings by PropertyId?

I'm working on something similar, and not having much success. It looks like the linking between schemas only supports a one-to-one mapping. That is, its expecting your Property to have a single BookingId that it can use to get booking details from the Booking schema... one-to-one.

Yogu commented

Hi @lifeiscontent,

graphql-weaver currently only supports forward links. This means that the join id must be available in the place where you want to create the link. In you example, this applies to the field Booking.propertyId. With the following config, you can link from bookings to properties:

const schema = await weaveSchemas({
    endpoints: [
        {
            namespace: 'properties',
            typePrefix: 'Property',
            url: 'https://v7l45qkw3.lp.gql.zone/graphql'
        },
        {
            namespace: 'bookings',
            typePrefix: 'Booking',
            url: 'https://41p4j4309.lp.gql.zone/graphql',
            fieldMetadata: {
                'Booking.propertyId': { // type name and field name in the *original* schema
                    link: {
                        field: 'properties.propertyById', // field path in the *weaved* schema
                        argument: 'id', // argument of properties.propertyById
                        batchMode: false, // propertyById returns one property and not many
                        linkFieldName: 'property' // name of the field in Booking where the object should be made available
                    }
                }
            }
        },
        {
            namespace: 'weather',
            typePrefix: 'Weather',
            url: 'https://5rrx10z19.lp.gql.zone/graphql'
        }
    ]
});

This allows you to do a query like this:

query {
  bookings {
    bookings {
      propertyId
      id
      startTime
      property { # the new field
        name
        location {
          name
        }
      }
    }
    
  }
}

This is quite inefficient, because propertyById needs to be queried once per referenced property. If you add a version that accepts a list of ids and returns a list of properties, this can be sped up significantly.

If you add a field with a relay-style filter argument that allows arbitrary filtering, you then can filter bookings by their properties. This also works for orderBy and first. Configure it like this:

const schema = await weaveSchemas({
    endpoints: [
        {
            namespace: 'properties',
            typePrefix: 'Property',
            url: 'https://v7l45qkw3.lp.gql.zone/graphql'
        },
        {
            namespace: 'bookings',
            typePrefix: 'Booking',
            url: 'https://41p4j4309.lp.gql.zone/graphql',
            fieldMetadata: {
                'Booking.propertyId': { // type name and field name in the *original* schema
                    link: {
                        field: 'properties.allProperties', // field path in the *weaved* schema
                        argument: 'filter.id_in', // argument of allProperties which accepts a list of ids to be matched
                        batchMode: true, // allProperties returns multiple properties
                        keyField: 'propertyId', // now needed to match properties back to their ids
                        linkFieldName: 'property' // name of the field in Booking where the object should be made available
                    }
                },
                'Query.bookings': {
                    join: {
                        linkField: 'property'
                    }
                }
            }
        },
        {
            namespace: 'weather',
            typePrefix: 'Weather',
            url: 'https://5rrx10z19.lp.gql.zone/graphql'
        }
    ]
});

Then, to only find bookings in California, ordered by city name, you could use this (assuming the property schema already offers this filter functionality), use a query like this:

query {
  bookings {
    bookings(filter:{ property_address_state: "California"}, orderBy: property_address_city_ASC) {
      propertyId
      id
      startTime
      property {
        name
        location {
          name
        }
      }
    }
    
  }
}

@adamkl: graphql-weaver supports one-to-one and one-to-many relationships, the latter via a field that contains a list of foreign keys. It does not yet support backwards links (how you usually would do a merge in SQL-like fashion). But if the properties schema in this example here would have a list of booking ids, graphql-weaver could link it to the respective bookings.

@Yogu Yes, I was able to get the one-to-many link to work the way you explained, with a list of foreign keys. That can put some constraints on the back-end data model, and, as you said, it is at odds with how you would do the same in SQL.
I'm thinking that backward links would be a good feature to add (especially since both @lifeiscontent and myself both ran into issues trying to do just that).
I'd definitely like to help you guys with this, I'm just going through some approvals on my end to get permission to contribute on work time.

Yogu commented

@adamkl That's great! Let's discuss how to implement this feature once you got approval to contribute.

@Yogu So I've been working a bit on this on my own (still waiting for approvals) and I've actually got it working in a very preliminary form. Just needed to change a couple of lines of code.

Still needs work testing out difference scenarios (I can already think of a limitation that should be resolved), documentation, and I'd like you guys to take a look obviously, so should I push my branch to your repo for now?

Yogu commented

hI @adamkl, that's really great news! You can fork this repo, push to your repo and then create a pull request. If you're unsure whether your employer allows the contribution, please write that again in the pull request.

Just a quick update on this. I looks like approval should come sometime next week, so I've already created a fork and I'll be getting my changes ready. Look for a PR sometime next week I hope.