Graphql-go: Feature Request: Schema Stitching. Combining multiple schemas into one

Created on 30 Oct 2017  路  19Comments  路  Source: graph-gophers/graphql-go

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

help wanted

Most helpful comment

@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

All 19 comments

Nice approach for composing a large application.

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.

@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

Any updates on this?

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

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

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?

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

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

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

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

Any chance this could include schema federation?

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

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

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.

@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.

Any updates since then ?

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

@aeramu Yes!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Melaninneal picture Melaninneal  路  5Comments

LeeWong picture LeeWong  路  4Comments

rogchap picture rogchap  路  5Comments

jakubdal picture jakubdal  路  6Comments

bsr203 picture bsr203  路  6Comments