Replies: 1 comment 1 reply
-
@andimarek Sorry I'm only getting to this now. Thanks a lot for the writeup! I think it's really helpful to lay out the differences in this way. I'm (still...) working on my talk for GraphQLConf, but the goal of that talk is to explain the design principles behind Federation and compare it to other approaches. It's definitely not the only way of doing things, but I hope to explain the reasoning behind some of the choices we made. Also looking forward to your talk, and to continuing this discussion in person. See you next week! |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This write up is inspired from #277. I thought it would be good to document the approach a bit more comprehensively.
Overview
Nadel is the execution engine powering the Atlassian GraphQL Gateway. Development started in the beginning of 2018 and I believe it was the or one of the first general purpose solution for distributed GraphQL.
It is actively developed and used in production since then.
I will call the approach which this WG aims to standardize "federation" as I understand it is heavily influenced by Apollo federation. I know it is not exactly the same and there will be differences between Apollo federation vs this spec, but the fundamental principles are the same. (I need to mention that I am not an expert on federation and I have not followed every detail of this WG here, so please correct me if some assumptions/statements are wrong here).
Subgraph vs Supergraph driven
The first fundamental difference is that the Supergraph in federation is the result of a composition of the Subgraphs. In Nadel the Supergraph is a mostly manually defined seperate GraphQL schema and there is not really any complex composition happening.
This also means that we always have to deal with two separate schemas per underlying service: the Subgraph one and the Supergraph one.
The total Supergraph schema is generated by a nearly trivial composition.
I hope this example makes it clear:
In this example we have two services and in total four schemas. The Supergraph and Subgraph schemas are actually exactly the same currently (later examples will have differences between them).
The overall Supergraph (the composition of the service specific Supergraphs) is generated by a trivial composition: just merging the fields of the Query type into one Query type and combining all other types into one schema:
Complex transformations of arbitray schemas
One of the main reason why the Supergraph schema is completely seperate from the Subgraph schema is that Nadel allows for complex transformations that changes the actual Gateway exposed schema significantly vs the implemented schema.
Here is such an example where the exposed schema is very different from the actual implementation in the Subgraph:
The changes are:
You can also imagine how in a federated schema you have directives available that allows for certain transformations. But I believe there is a limit where a transformation applied to a schema is so complex that having actually two seperate schemas with instructions how to go from one to another is a better solution.
Another main reason for having a seperate Supergraph schema vs Subgraph schema is that from the beginning we had the requirement to handle already existing GraphQL APIs, which could not be easily changed. Forcing these APIs to add specific directives onto their schema (or other changes) in order to be able to exposed via Gateway was not feasible sometimes. (The existing APIs had already existing consumers, which would access them directly, not via Gateway)
Unique owner and hydration
Another difference to federation is that each type (and field per type) is clearly owned by exactly one service.
Federation example with a type owned by two services:
This is not allowed in Nadel, but the same Supergraph schema is produced by this:
Here the Inventory service extends the Product type with a new field
inStock
, which is "hydrated" by querying theisProductInStock
root field. The@hydrated
directive tells Nadel how to resolve theinStock
field value at execution time.We never came across the need to actually define a type in multiple services: one type having exactly one owner was always very clear and normally the ownership is very naturally clear based on the domain of the service. In this example there is no need for an Inventory service to be concerned with an actual Product: having a product reference via ID is enough.
Another feature of federation Nadel doesn't support is fields shared/provided in multiple services: it was simply never needed.
Shared types
As mentioned before there is really no composition happening on the gateway level. If you have shared types across services they are defined once at the Supergraph level.
Here is an example where an input
Location
is shared across multiple services:The location type is defined once on the Supergraph level and simply referenced in the Supergraph schemas per service. Additionally it is duplicated per Subgraph schema.
The consequence of that is that the Supergraph schemas are not actually fully valid standalone GraphQL schemas: only after (trivial) composition a fully valid overall Supergraph schema is available.
The location types per underlying service must be compatible with each other, but they can be different.
For example the Subgraph Customer schema could have a nullable second field in Location:
But this
country
field is never exposed via the gateway so all request will alwayscountry: null
as value.Pure GraphQL
Nadel requires no special features for underlying services to be able to contribute to the exposed overall Supergraph. But I believe that this WG aims also for just GraphQL as requirement.
Two schemas per service is not always ideal
I believe the model of having a seperate Supergraph schema from the Subgraph schema is very powerful model. It gives you a very easy to reason about model even when you need to apply complex transformation. It served us very well over the years, but it comes definitely with some costs.
If you develop a service from the start with the goal being exposed via the Gateway, the Nadel model is often unnecessary complex and maintaining two different Schemas per underlying service is more work than just having one. We provide tooling to ease this work, but this again requires work.
I left out quite a lot of things, but I hope that serves as an overview how our approach to distributed GraphQL works.
cc @martijnwalraven @Urigo @michaelstaib @dventimihasura
Beta Was this translation helpful? Give feedback.
All reactions