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