gramps-graphql / gramps--legacy

The core data source combination engine of GrAMPS.

Home Page:https://gramps.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add support for schema stitching

jlengstorf opened this issue · comments

(Splitting the schema stitching conversation off from the larger discussion in gramps-graphql/gramps-express#39 to help concentrate individual discussions.)

From @ecwyne:

But my sense is that staying as close to the Apollo api will serve GrAMPS well in the long run.

I agree with this 100%. My intention wasn't to suggest that GrAMPS define its own schema stitching API, but rather to suggest that we need to think through how to define the two-way bindings described in the schema stitching docs.

My gut says that the most sustainable way to do this — if we use the User/Chirp example — would be to have the User data source define something like this:

const dataSource = {
  namespace: 'User',
  schema: /* ... */,
  resolvers: /* ... */,
  mocks: /* ... */,
  model: /* ... */,
  stitching: {
    requires: ['Chirp'],
    linkTypeDefs: `
        extend type User {
          chirps: [Chirp]
        }
    `,
    resolvers: mergeInfo => ({
      User: {
        chirps: {
          fragment: `fragment UserFragment on User { id }`,
          resolve(parent, args, context, info) {
            const authorId = parent.id;
            return mergeInfo.delegate(
              'query',
              'chirpsByAuthorId',
              {
                authorId,
              },
              context,
              info,
            );
          },
        },
      },
    },
  }
};

The Chirp schema would be required to define it's own stitching object to enable the author field within the Chirp schema.

The requires field would allow us to check for the existence of required data source(s) before adding the schema stitching pieces to our complete executable schema. (And again, this would be only for local data sources — if stitching with an external data source, that would happen outside of GrAMPS.)

The API above is not necessarily the best way to do this — it's just the first one I thought of. But the main point I'm getting at is the underlying concept: each data source is responsible for stitching its half of the equation.

This is very close to the API I would suggest as well, with one exception. We discussed using peerDependencies to declare what a data source "requires".

If we add an additional "Comments" schema to your above example (where a user can see all of their comments)

const dataSource = {
  namespace: 'User',
  schema: /* ... */,
  resolvers: /* ... */,
  mocks: /* ... */,
  model: /* ... */,
  stitching: {
    requires: ['Chirp', 'Comment'],
    linkTypeDefs: `
        extend type User {
          chirps: [Chirp]
          comments: [Comment]
        }
    `,
    resolvers: mergeInfo => ({
      User: {
        comments: {
          fragment: `fragment UserFragment on User { id }`,
          resolve(parent, args, context, info) {
            const authorId = parent.id;
            return mergeInfo.delegate(
              'query',
              'commentssByAuthorId',
              {
                authorId,
              },
              context,
              info,
            );
          },
        },
        chirps: {
          fragment: `fragment UserFragment on User { id }`,
          resolve(parent, args, context, info) {
            const authorId = parent.id;
            return mergeInfo.delegate(
              'query',
              'chirpsByAuthorId',
              {
                authorId,
              },
              context,
              info,
            );
          },
        },
      },
    },
  }
};

What should I do if Chirps is present but not Comments?

peerDependencies would make these requirements all-or-nothing and provide SemVer assurances out of the box.

Oh, shit, I totally forgot about the peerDependencies conversation.

Yes, that's better. Let's do that. 😄

@jlengstorf I'm going to take a crack at this today. Do you prefer:

const dataSource = {
  namespace: 'User',
  schema: /* ... */,
  resolvers: /* ... */,
  mocks: /* ... */,
  model: /* ... */,
  stitching: {
    linkTypeDefs: /* ... */,
    resolvers: mergeInfo => /* ... */,
  },
};

or

const dataSource = {
  namespace: 'User',
  schema: /* ... */,
  resolvers: /* ... */,
  mocks: /* ... */,
  model: /* ... */,
  linkTypeDefs: /* ... */,
  stitchResolvers: mergeInfo => /* ... */,
};

Awesome. I think I prefer the first option. It seems clearer to me.

Closed by #12