-
Notifications
You must be signed in to change notification settings - Fork 242
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
[Federated] Services namespaces #350
Comments
anyone? |
I'm currently having the same problem in my gateway. Managing type conflicts administratively is not an option either. Do you guys have any idea / roadmap /design doc to support this kind of feature or I should try to implement myself ? I the answer is the later, I think I could try implementing and open sourcing the final code |
@tlgimenes I was thinking abut this a lot and yet didn't figure out how to make this possible. The simpliest as far as I think now way is to transform (rename types and nest root types) schemas at service level but again - if you don't control it this is not an option. Make a transforming proxy inbetween gateway and service? Overhead and should break relation resolution (but maybe there is a way to proxy this also) Fork gateway and apply transforms on schema construction level? Again, relation resolution will fail. Also there is a place where this can be done (best option maybe) — I'll try to experiment with this but at the moment I have no certain understanding of how it is possible |
My current attempt is to transform schema at diff --git a/packages/apollo-federation/src/composition/types.ts b/packages/apollo-federation/src/composition/types.ts
index 62538a2c..aa52d64c 100644
--- a/packages/apollo-federation/src/composition/types.ts
+++ b/packages/apollo-federation/src/composition/types.ts
@@ -37,6 +37,7 @@ export interface ServiceDefinition {
typeDefs: DocumentNode;
name: string;
url?: string;
+ namespace?: string;
}
declare module 'graphql/type/definition' {
diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json
index 2e51962c..fffc9d1b 100644
--- a/packages/apollo-gateway/package.json
+++ b/packages/apollo-gateway/package.json
@@ -28,6 +28,7 @@
"apollo-server-env": "file:../apollo-server-env",
"apollo-server-types": "file:../apollo-server-types",
"graphql-extensions": "file:../graphql-extensions",
+ "graphql-tools": "^4.0.5",
"loglevel": "^1.6.1",
"loglevel-debug": "^0.0.1",
"pretty-format": "^24.7.0"
diff --git a/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts b/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts
index 06a2a0b3..552e0d1b 100644
--- a/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts
+++ b/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts
@@ -1,9 +1,10 @@
import { GraphQLRequest } from 'apollo-server-types';
-import { parse } from 'graphql';
+import {parse, printSchema} from 'graphql';
import { Headers, HeadersInit } from 'node-fetch';
import { GraphQLDataSource } from './datasources/types';
import { UpdateServiceDefinitions } from './';
import { ServiceDefinition } from '@apollo/federation';
+import {makeExecutableSchema, RenameTypes, transformSchema} from "graphql-tools";
export async function getServiceDefinitionsFromRemoteEndpoint({
serviceList,
@@ -13,6 +14,7 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
serviceList: {
name: string;
url?: string;
+ namespace?: string;
dataSource: GraphQLDataSource;
}[];
headers?: HeadersInit;
@@ -27,7 +29,7 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
let isNewSchema = false;
// for each service, fetch its introspection schema
const serviceDefinitions: ServiceDefinition[] = (await Promise.all(
- serviceList.map(({ name, url, dataSource }) => {
+ serviceList.map(({ name, url, namespace, dataSource }) => {
if (!url) {
throw new Error(`Tried to load schema from ${name} but no url found`);
}
@@ -53,10 +55,25 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
isNewSchema = true;
}
serviceSdlCache.set(name, typeDefs);
+
+ let serviceTypeDefs;
+ if (namespace) {
+ const schema = transformSchema(makeExecutableSchema({typeDefs}), [
+ new RenameTypes(
+ (typename: string) => `${namespace}${typename}`,
+ {renameBuiltins: false, renameScalars: true}
+ )
+ ]);
+ serviceTypeDefs = parse(printSchema(schema));
+ } else {
+ serviceTypeDefs = parse(typeDefs);
+ }
+
+
return {
name,
url,
- typeDefs: parse(typeDefs),
+ typeDefs: serviceTypeDefs,
};
} And it works fine until you try to make relations between services. Then everything fails (based on https://github.com/apollographql/federation-demo):
|
As far as I think now the biggest problem is that service extending types of other services and they could not be correctly namespaced because you don't know from what service is it |
Ok, this version namespaces the types and build a proper schema (without nesting but better then nothing) Direct queries in services work, relations — don't (return Based on @tlgimenes @abernix a look and/or some suggestions for next moves? |
Can't we use the name of the namespace in the relation directives to make it work ? I still didn't have the time to look at it but I think if we use a parameter of the namespace in the relation directives we could make relations work by correctly transforming the types. And I think that using the namespace in the directive is more than fair, don't you think ? |
@tlgimenes yeah, in this case directive should use namespaced type. btw I've didn't manage to make apollo federation to work properly with namespaces (schema ok, but relation requests — not) so I'm building own gateway on top of federation protocol |
I know this is sort of a side note (but can't figure out where to post). How were you @terion-name able to get the resolvers from the auto-generated CRUD from Prisma. |
Currently, yes, the recommended strategy is to alter the downstream schema which, yes, is not the best answer if you don't control it. We're looking at various ways to support namespaces rn |
@queerviolet I've fully rewritten gateway (in fact wrote it from ground up) to get namespaces and other features like advanced customisable relations, multi tenancy etc. This can be done, but I don't think thatt current apollo server gateway design can be altered without massive rewrite |
this would definitely be a nice to have for on-boarding to federation. i'm currently in the in-between state of can and can't administratively manage the schema. |
Any updates on this? |
@queerviolet Is there any updates on this? |
Any updates on this? |
Doesn't For example: # "Loan" service
# The subject of a loan (typically an individual or a business)
type Subject implements Node {
id: ID!
...
}
# The "global" variant of subject, the Auth version of a subject
extend type Subject implements Node @key(fields: "id") {
id: ID!
... @external
} When running As an example, you might see the type names get converted to something machine-readable like Then, in the gateway, you add the service, with an option for the While this idea might work, it might not necessarily be the easiest to read, and it might be difficult to understand at a glance from which service this type originates. Maybe it would be useful for the sake of preventing global namespace collision and readability to specify something like: # similar to declaring external fields, you can declare the type as external and
# the name of the service it will use in federation
[extend] type Subject implements Node @external(service: "auth") @key(fields: "id") {
id: ID!
... @external
} Note: This might make manually declaring a service's "namespace" irrelevant, as the gateway should theoretically have enough information to detect and resolve namespace collision this way, with the caveat of it theoretically being necessary to declare an "authoritative" type if a collision occurs. In other words, by default, definitions are globally scoped and not namespaced (unless maybe you state you want all namespaces to be added 100% of the time when constructing the runtime schema in the gateway, for example For example, let's say Whether or not the As a concrete example: # Loan Service
type Subject implements Node {
id: ID!
...
}
type Subject implements Node @external(service: "Auth") @key(fields: "id") {
id: ID!
... @external
} Which would get converted by # Loan Service
type __INTERNAL__Subject implements Node { # or maybe use an under-the-hood directive, like @apolloFederationInternal, instead of changing the name
id: ID!
...
}
type __EXTERNAL__Subject implements Node @external(service: "Loan") {
id: ID!
... @external
} Which when added to a federated schema: const serviceList = [{ name: 'Auth', url: '...' }, { name: 'Loan', url: '...' }] Would produce an error:
Because you need to declare one as # Auth Service
type Subject implements Node @primary(deprecation: "20XX-01-01") {
...
} At which point the federated schema would look like: # Auth variant
type Subject implements Node @deprecated(reason: "Transition to AuthSubject by 20XX-01-01") {
...
}
# Automatically namespaced Auth variant, only appears if deprecation is enabled on the above variant
type AuthSubject implements Node {
...
}
# Automatically namespaced Loan variant
type LoanSubject implements Node {
...
} |
Hi, there's a long time no update on this issue. Is there any thoughts or update about it yet? |
Yeah, it would be interesting to hear more about how people are solving this. We built our API gateway using schema stitching ~3 years ago and just blanket namespaced all types + root fields, e.g. // api-gateway service
const rawSchema = await getRemoteSchema(usersLink);
return transformSchema(rawSchema, [
new RenameTypes(name => `Users${ucFirst(name)}`),
new RenameRootFields((operation, name) => `users${ucFirst(name)}`),
]); It's worked fine, but makes our API a bit grim to read, e.g. query {
usersUser(id: "usr_123") {
__typename # UsersUser
id
locale {
__typename # UsersLocale
region
language
currency
}
}
ordersOrder(id: "odr_123") {
__typename # OrdersOrder
id
locale {
__typename # OrdersLocale
region
language
currency
}
user {
__typename # UsersUser (delegates to the users service)
id
}
}
} Is this a recommended approach and if so, is it easily done with Apollo Federation? I guess we could just perform the transformation in the subgraph so the gateway doesn't need to know about it? |
Hi all, I'm the Product Manager for the Apollo Federation Core team 👋 We're currently researching this request, and we'd love to hear any more feedback/requirements for namespaces in Federation. There's a lot of great detail in this thread, but we wanted to open the door for more candid feedback -- where we can dive into your specific use-cases and concerns. With that, if you'd like to discuss this issue further, please feel free to either schedule a call directly on my Calendly link, or email me at korinne@apollographql.com. Thanks in advance! |
Currently I have a system built with microservices and with a self-written gateway that uses schema-stitching and a lot of magic.
This system was written several years ago and now needs a refresh and Apollo Federation has most of features that I've implemented by my own but better designed. Now I want to migrate to Federation but it seems to a problem.
My usecase maybe specific but I think it is not uniquely rare.
In short: services should have namespaces.
Namespaces are needed to prevent type collisions. The idea behind is that services are autonomous, are written by numerious developers, including external ones. And services can (and do) have alike types (e.g.
News
hasArticle
andBlog
hasArticle
).Controlling types uniqueness "administratively" is not an option due to big amount of services and people involved. Also considering auto-generated CRUD types (e.g. from
Prisma
) this is becoming even more of a problem. To 100% eliminate type collisions and bring a unified convention to end-users gateway should namespace services.In practice it looks like this:
Services:
At the gateway schemas are transforming before stitiching and result is like this:
This approach proven to be convenient with tens or hundreds of services with hundreds or thousands of types and with autogeneration on services level.
Looking at Apollo Federation I don't see a possibility to do this. So the questions:
The text was updated successfully, but these errors were encountered: