Skip to content

Feature Request: Schema Stitching. Combining multiple schemas into one #120

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

Closed
arquio opened this issue Oct 30, 2017 · 20 comments
Closed

Feature Request: Schema Stitching. Combining multiple schemas into one #120

arquio opened this issue Oct 30, 2017 · 20 comments

Comments

@arquio
Copy link

arquio commented Oct 30, 2017

Hi,

are there any plan for implemente this feature or similar??

Thanks a lot for all yours work.

Ref: https://dev-blog.apollodata.com/graphql-schema-stitching-8af23354ac37

@mikeifomin
Copy link

Nice approach for composing a large application.

@jackielii
Copy link

jackielii commented Nov 17, 2017

I was looking at this just now. I have an idea:

e.g. I have UserSchema

type UserResolver struct{}
func (UserResolver) UserByID(id string) *User

and then OrderSchema

type OrderResolver struct{}
func (OrderResolver) OrderByID(id string) *Order

then the combined schema resolver I can use:

type RootResolver{
  UserResolver
  OrderResolver
}

But unfortunately it doesn't work, and it's not very easy to fix it.

@albertorestifo
Copy link

@jackielii on top of that, schema stitching should allow for more complex merging:

If I have the following two remote GraphQL schemas:

type Event {
  date: String
  location: String
}


Query {
   event(date: String): Event
}
type Weather {
  temperature Int
  condition String
  windSpeed: Int
}

Query {
  forecast(location: String): Weather
}

I should be able to stich them toghether and perform the following query:

query GetEvent($date: String) {
  event(date: $date) {
    location {
      temperature
    }
  }
}

That's where the power of stitching comes in use

@ghost
Copy link

ghost commented Jan 10, 2019

Any updates on this?

@mununki
Copy link

mununki commented May 16, 2019

Actually, I built a tool to merge and stitch modularized *.graphql files into one schema in Go. I hope my tool to help you out.

https://github.com/mattdamon108/gqlmerge

@obukhov
Copy link

obukhov commented Jun 1, 2019

I've made some research and found out that schema stitching is now deprecated feature of graphql-tools and is replaced with apollo federation: https://blog.apollographql.com/apollo-federation-f260cf525d21

@obukhov
Copy link

obukhov commented Jun 1, 2019

So maybe now effort should be more on making build-in compatibility for federation as described here: https://www.apollographql.com/docs/apollo-server/federation/federation-spec/.
What do you think?

@jakubknejzlik
Copy link

@obukhov I definitely agree. Also as we are moving our "federated" services to golang, it would be awesome if I can help 👍

@jakubknejzlik
Copy link

If anyone interested, I've found already implemented solution (for "http link" check the whole gateway repo): https://github.com/nautilus/gateway/blob/master/merge.go

@pavelnikolov
Copy link
Member

And one of the key prerequisites in order to deliver this is #345 which is few unit tests away from getting merged.

@keithmattix
Copy link

I'm curious as to the progress of this feature since the merging of #345

@tim-white87
Copy link

Any chance this could include schema federation?

@pavelnikolov
Copy link
Member

@cecotw Ideally, yes. I'd like this library to support federation at some point. Maybe through an additional module or middleware.

@CreatCodeBuild
Copy link

CreatCodeBuild commented Dec 1, 2019

Hi all,

Maybe Federation isn't necessary. We can solve the schema sharing/remote schema/remote resolver problem from a different angle.


Background story:

My company has been running GraphQL in production at a large scale (~1/10 billion requests per day) for over a year. Recently we had the need to merge 2 schemas because we have a new product line. Since Apollo Federation is out, my team deployed that. It's likely that my company and my team is one of first companies which deploy Federation on a large scale.

However, Federation introduces this 3-way coupling. You have to modify both data provider (downstream schema & resolvers), data consumer (upstream schema & resolvers) and gateway (only the first time) to join the federated schema. It's super weird that the data provider has to know the data consumer.

Also, because of the centralized gateway, our current distributed monitoring & tracing solution doesn't work anymore. I still haven't figured out how to make it work (without buying Apollo's Graph Manager SaaS). Therefore, we haven't deploy federation to production yet. We are playing with it in staging.

Additionally, since Apollo Server is the only implementation of federated schema, I can't join schema written in other languages such as Go. But we all know that Go graphql server has higher performance and is actually faster to develop than NodeJS onces.

See blog post Why we moved our graphQL server from Node.js to Golang
(I'm not the author. I'm just citing his work.)


Out of my own frustration, I ask myself: Why not solve this problem in another way. Therefore, I wrote this super small library that let you execute remote resolvers as if they are local.

https://github.com/CreatCodeBuild/deno-graphql/blob/master/remote-graph/readme.md

I originally intended to write it in Go with graph-gophers. but end up in JS for 2 reaons:

  1. Reflection is hard in Go and will take more time initially. I want to see if my approach works first.
  2. JS has more active GraphQL community so that I can gather more feedbacks.

This lib has Zero Modification principle. That is, you don't need to change anything in the data provider(downstream schema) and as a by-product, you can

  1. compose public GraphQL API which you don't own into your own schema.
  2. compose schema implemented in other languages.

Here is a short example. Assuming you are writing a User service.

# User Service
type Query {
  user: User
}
type User {
  name: String
  products: [Product]  # Assuming Product is defined in another server.
}
# You only need to define the parts of Product you want to use in this service.
# Maybe Product has 10 fields, but you only need 2.
type Product {
  id: ID
  price: Float
}

Assume Product service has this schema

# Product Service
type Query {
  getProducts: [Product]
}
type Product {
  id: ID
  price: Float
  ... other fields ....
}

Here is how you define the resovler

// User Service
let resolvers = {
        user: {
                 name: ()=>{...}
                 products:  await RemoteType(
                      HTTP('product.your-domain.com'),
                      `query`,
                      `getProducts`
                );
        },
};

Done. You don't need to change how you write queries. It just works.

HTTP and RemoteType are the only 2 helper functions you need to use.

RemoteType introspect the schema HTTP points to, and check if local type matches remote type. If so, it creates a resolver that is intelligent enough to parse info object and generate the correct queries to fetch data from downstream GraphQL services.

It also supports:

  1. Batching. Yes, it works with dataloader. Just swap RemoteType with BatchedRemoteType
  2. Authentication by http headers
  3. Custom transport. HTTP is just a Transport interface. You can implement different transports. In-memory for example.
  4. Full GraphQL Query langauge. (not yet right now but it's super easy to implement)
  5. Type renaming. You can modify the type name. For example, you can define ProductOfUser instead of Product in User service.
  6. Field modification. You can add extra arguments on a remote field in your local schema definition. It just works.

Todo:

  1. I want to support a nice directive syntax. For example
type Query {
    me: User
}
type User @remote(url: "github.com/graphql-api", entry: "Query.viewer") {
    login:  String
    age:    Int @local  # should allow local schema to expand a remote type.
}
  1. Subscription
  2. Remote schema auto-generation. Instead of copy & paste remote schema into your local. Maybe I should write a tool to generate a schema language source from the introspected schema.

I have tested it with Github and 2 other public GraphQL APIs and it just works. Therefore, it's possible to write a Go package with the same approach.

That been said, I need your feedback.

Full example here: https://github.com/CreatCodeBuild/deno-graphql/blob/master/remote-graph/integration-tests/example.ts

@pavelnikolov
Copy link
Member

I think that it is a little different use case. Imagine that the names of the types are the same. Then, some form of extension would be required. Ideally, this should happen declaratively and not imperatively.

@CreatCodeBuild
Copy link

@pavelnikolov Did you mean something like

# local schema
type Query {
  loggedIn: LoggedInUser # points to remote Query.userBy
}
# remote schema
type Query {
  userBy(id: ID): User
}

This is not a concern for remote-graph because it purely operates on field/resolver level. It does not load remote schema into local schema. Put it in other words, it sort treats remote types as interfaces in which as long as the shape matches, it accepts.

@sh0umik
Copy link

sh0umik commented Apr 19, 2020

Any updates since then ?

@aeramu
Copy link
Contributor

aeramu commented Mar 14, 2021

99design's gqlgen already support compatibility with apollo federation. Is there any plan to develop compatibility with apollo federation too?

@pavelnikolov
Copy link
Member

@aeramu Yes!

@pavelnikolov
Copy link
Member

I am closing this issue as #507 already allows you to make use of Apollo Federation and use this library to build the subgraphs in Go. A more advanced federation where one subgraph can extend another subgraph is not supported yet. I hope that we'd add that in future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests