Open mxstbr opened 11 months ago
Related to #89; potentially data sources could also be the solution for stitching at least? 🤔 (probably not true federated subgraph support though) cc @JoviDeCroock
With existing GraphQL APIs as a migration path to Fuse.js.
There are a few scenario's of where people could have an existing GraphQL API, listing them up here
I think the first two could quite easily be solved by leveraging schema-stitching
where the existing (micro-)service would be a stitched endpoint with its own executor.
For Federation this becomes a bit more complex as either we become the router, which has a lot of complexities involved as is or we stitch in the router. With the former approach I feel like there isn't much prior art in terms of doing schema-extensions from the router, which in this case we would most likely need to provide value over just using federation... The latter approach I haven't looked too deep into.
By going with the stitching approach we would need to solve the directional issue of connecting both graphs, where in the stitching world this is done by means of programatically specifying the touching points i.e.
stitchSchemas({
subschemas: [
{
schema: schema1,
merge: {
Entity: {
fieldName: 'entityFromSchema1ById',
selectionSet: '{ id }',
args: obj => ({ id: obj.id })
}
}
},
{
schema: schema2,
merge: {
Entity: {
fieldName: 'entityFromSchema2ById',
selectionSet: '{ id }',
args: obj => ({ id: obj.id })
}
}
}
],
})
this basically needs us to specify how to merge the same entity of schema1 with the one from schema2 and how each of these schemas queries them. From our point of view we could do this both with node
as well as the automatically generated entry-point so in that regards we should have a pretty easy job of creating heuristics for these merges, however it might not be as easy for the unknown schema's coming in and would require a lot of checks in dev
mode and a pre-compile mode in build
.
From a runtime perspective all of this seems very solve-able, this however brings me to the editor experience, early on in the fuse
design process we opted to not expose the SchemaBuilder
to the user and instead provide our own set of plugins and expose our own set of functions that we think you need to build a good API.
In theory we can add all these types in the SchemaBuilder
generic which we don't have access to or isn't really dynamically insert-able. A solution to that could be that we override the Pothos.defaultTypes
in a global type, which would be similar to how we extend the FieldBuilder
for the list()
as seen here.
Not sure yet about the typing path but this could be a way forward.
Pothos does offer the Add
plugin for embedding types, however reasoning more about connecting graphs in stitching it might not be needed as types can be merged, which means that when we create a node
or an object
that in theory it can just be merged, the only question-mark arising here is how the global-ids will interact...
Sorry for the braindump 😅
I wonder if we could connect this with #89; conceptually, you could see another GraphQL API (whether a full graph or a subgraph) as a data source for your data layer.
Another thought is that potentially stitching a whole schema into the data layer isn't the right way to think about it. Maybe a way to think about it that'd feel more native would be to say a node can be sourced from a type from another GraphQL API.
That could look something like:
export const GitHubRepositoryNode = node({
name: 'GitHubRepository',
datasource: new GraphQLDatasource({
url: gitHubAPIUrl,
schema: …, // Required if `url` isn't introspectable or something?
type: 'Repository' // Source for this node is the Repository type of the underlying schema
}),
fields: (t) => ({
// Has access to all scalar fields
name: t.exposeString('name')
owner: t.exposeString('owner')
issue: t.expose('issue', {
// Tradeoff: Connections to other types requires implementing a node for them, too
type: GitHubIssue,
})
})
})
export const GitHubIssue = node({
name: 'GitHubIssue',
datasource: new GraphQLDatasource({
url: gitHubAPIUrl,
schema: …,
type: 'Issue'
}),
fields: (t) => ({
number: t.exposeInt('number')
})
})
The main tradeoff (as noted in the code comment) is that connections to other types require implementing a node for them, too which is obviously more work than "just" stitching a whole API into another API.
Summary
Many companies that use GraphQL at scale today use GraphQL Federation, which essentially composes many microservices that expose a part of the GraphQL schema.
When there is a backend team that's already using GraphQL and liking it, being able to stitch their schema into the data layer might be appealing.
Another use case is third-party APIs that are already GraphQL.
Another use case is companies that are already using GraphQL and want to stitch their existing API into Fuse—but I'm unsure why they would use Fuse.js on top of that 🤔
Proposed Solution
Fuse.js could either: