Background
In the scenario GraphQL Federation was designed for, you have numerous GraphQL microservices in the backend, each with their own Schema Definition Language (SDL).
Theoretically, these services are able to start and run independently and can begin serving GraphQL requests right away. In order to tie these services together, you also must have a GraphQL gateway service that obtains knowledge of the disparate SDLs (via service discovery or some other mechanism) that is able to reconcile these SDLs and present a unified API for consumers. This is nice, since the consumers never have to know about the underlying services providing the data --all they see is one unified graph.
Problem
The problem here is that GraphQL Federation essentially requires that the backend services have knowledge of one another and the kinds of entities that exist in the graph. In Apollo Federation, every entity in the graph must belong to a canonical 'owner' service. For instance, anything related to a User
entity would be provided by a User
service. If you've modelled your graph such that a User
can have many Products
, and the Products
portion of the graph is provided by a Products
service, then you'd use special directives (like @external
) to indicate to the gateway that an external service provides that data.
Example
To illustrate, here is a User
GraphQL schema that references an external entity, Product
:
type Query {
user: User
}
type User {
id: ID!
name: String
products: [Product] # Products is defined in the `Products` service.
}
type Product {
id: ID! @external
}
Then in the Products
service, you'd simply define a Product
:
# The `Products` GraphQL service
type Query {
Product: Product
}
type Product @key(fields: "id") {
id: ID!
name: String
upc: String
}
Question
My question is: Isn't this an architectural smell?
The User
service is now tightly coupled to the Products
service. The gateway must also be aware of implementation details of the other services, since it needs to understand Apollo Federation's custom annotations (@extend, @provides, @external), which means the gateway is now also tightly coupled to service implementations. If I wanted to change the Product
entity name into something else, for example PurchasedProducts
, I would have to touch the Products
service, the User
Service (as well as any of the other possibly dozens of services referencing Product
).
I've seen that many big orgs are now using federation in one form or another, many of them choosing Apollo's specific implementation --how are they dealing with this coupling issue? I thought the whole point of microservices was to decompose a monolith into small, independent pieces of consumer-agnostic logic that are easy to deploy and scale. But what Apollo Federation seems to encourage is a sort of distributed monolith architecture that requires a high amount of coordination in order to succeed. Isn't that an anti-pattern?