-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Fixes types for federation #4724
Fixes types for federation #4724
Conversation
🦋 Changeset detectedLatest commit: a17a8d3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Thank you @KoltesDigital . Can you please add a changeset file? (run @jakeblaxon it's related to changes you recently added. Do you mind give it a quick look? |
I've looked over the changes, but it looks like this PR would cause the issues in #4493 to reappear. A major reason is that it changes the Looking at #4722, I see that you're using mapped types. I didn't address the mapped type case at all when working on adding this federation functionality, so it may be buggy and there is definitely room for improvement there. For your extend type User @key(fields: "id") {
id: ID! @external
firstName: String! @external
lastName: String! @external
fullName: String! @requires(fields: "firstName lastName")
} (You will need to add all required external fields to your extended type, otherwise the Apollo Gateway will throw an error, hence why I've added them here). If you don't specify that these fields are required, then the Apollo Gateway may not provide them to you for certain requests. For example, what if I requested the following: {
getUser(...) {
id
firstName
fullName
}
} In this case, the Apollo Gateway will only request Since With this in mind, I would recommend closing this PR without merging and also closing #4722 as a misunderstanding (@dotansimha ). Please let me know if you have any questions regarding this though, and I'll be happy to help out more. This is complicated and counterintuitive stuff, so I definitely don't blame you for any misunderstandings. I myself have only gotten familiar with these details through many months of intimate trial and error. Thanks! |
I disagree with your understanding of federation. My understanding is as follow. With or without federation, the parent type is not required at all to mirror the GraphQL entity type. My example of User, without the federation, would be correct anyway: the JS object has Now, Apollo Federation exposes entities whose fields are served by different services. These different services may have different JS models, so they need to bind one to one. That's the purpose of In this example, Speaking of users and names, I admit it makes sense to have all these fields, but it's just the easiest example I found to show the bug. I just didn't wanted to call these fields foo and bar. But the basic principle is: JS models and GraphQL models are distinct, though they may overlap, they don't have to. Besides, the example is based on the project I'm working on for several months, with currently five federated services. For now the types are written by hand and it works as I claim. With the current codegen, it doesn't compile anymore. With my patch, it compiles (and the generated JS is obviously the same, as it's only an issue with TS type declarations). You can easily code a new project which replicates this example and verify my claims. That being said, I admit that this patch is based on my experience that every service have strong JS models. This might not always be the case: If we want to support both cases, I believe we need to distinct whether an entry is given in the mapper config. If there is a parent type (JS model), it means a |
Regarding #4493 (which I haven't searched for earlier, sorry!) I believe there is a modeling issue which leads to (or comes from) a wrong understanding of federation or even GraphQL itself. Service 1 should not give Searching online for "GraphQL best practices" give the same advice: https://atheros.ai/blog/graphql-best-practices-for-graphql-schema-design , https://towardsdatascience.com/graphql-best-practices-3fda586538c4 , etc. Given that, the JS model has a |
Ahh, I see what you're saying now. I was assuming that
Right now, only case 1 is accounted for, so this PR should seek to add case 2 while preserving case 1. Does this sound correct to you? If so, let's start looking into ways that we can do this. In the meantime, though, I'd convert this PR to a draft since it would break case 1. |
Actually, case 2 is a little more complicated since we can still use { __typename: 'User' } & GraphQLRecursivePick<ParentType, {"id":true}> & MappedType That way, if we provide { __typename: 'User' } & GraphQLRecursivePick<ParentType, {"id":true, "address":true}> & MappedType I need to test this to make sure this is what actually happens though. This gets pretty complicated intuitively when you can use |
The problem is, as a TS types provider, the tool can't generate types based on the way the types are used. I do not recommend a type union neither, because it would require adding a check in the TS file (otherwise TS would complain) but this check is useless because we know upfront that the parentType is either resolved or not. Regarding graphql-code-generator/packages/plugins/typescript/resolvers/tests/federation.spec.ts Line 119 in 39f71c9
That's why I mentioned relying on the mapper config key: if there's one, To be even more spec-compliant: without extend type T {
a: String!
b: String! @requires(field: "a")
c: String!
} export const resolvers: RoomTypeSchemaResolvers<any> = {
T: {
c(parent) {
return parent.a ? 'foo': 'bar';
}
}
}; Unless someone complains, I recommend to not care about this behavior :) |
@jakeblaxon Hi! Any news? |
I apologize for the delay on this. I just reviewed this thread and I agree with your solution. I think using the mapper config key to determine whether to require the With this in mind, we should be able to use most of what's currently in your PR. We just need to make a few adjustments to preserve current functionality and add the conditional check. Do you want to make the changes? I'll be able to review pretty frequently over the next couple of weeks, so just let me know when changes are ready and I'll take a look as soon as I can. Thank you for your patience! |
Hi! No worry, it's great to make progress on that. I'll try to make the change. I also remember that I was bothered by a test I changed but the "expected" result might not be what we actually expect, I'll have a look at that. And I'll make the changeset. |
39f71c9
to
a17a8d3
Compare
@jakeblaxon now as discussed. One thing I'm bothered with is that all parent types are generated, even when they are overriden by custom mapped types. I use to name my models the same as their GraphQL counterparts, thus it would conflict. It looks like the generation of these types leads to the generation of the related |
Hi! Is there any update on this? |
I'm reviewing it currently |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added few comments
`); | ||
}); | ||
|
||
it('should not add __resolveReference to objects that do not have mapped types in config.mappers', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not true. The __resolveReference
should be available for all types with @key
that are local to the service.
@@ -388,6 +447,9 @@ describe('TypeScript Resolvers Plugin + Apollo Federation', () => { | |||
schema: federatedSchema, | |||
config: { | |||
federation: true, | |||
mappers: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do you added mappers to all tests?
const typeMap = schema.getTypeMap(); | ||
for (const typeName in typeMap) { | ||
const type = schema.getType(typeName); | ||
if (isObjectType(type) && isFederationObjectType(type)) { | ||
if (isObjectType(type) && isFederationObjectType(type) && mappersTypeName && mappersTypeName.includes(typeName)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, __resolveReference
is used to resolve an a reference to an entity which means mappers can't change that behavior. Mappers are only for "us" to define an exact type instead of the default one from the schema.
We try to avoid any breaking changes and the thing I pointed out is one. We're going to rewrite federation support from scratch soon. |
Ok, I reviewed your changes and created a document about Federation and typescript-resolvers. Feedback needed https://www.notion.so/Federation-typescript-resolvers-3b47e825ff9a43b7a970c28e8784a6f7. This will help to rewrite the federation part in codegen. |
Created this PR #5645 to progress with Federation support step by step. |
Is there anything I can do to rebase or fix this up for merge? The bugs this fixes affect my usage frequently and I'm available to help with this if there's something I can do. |
@bsmedberg-xometry we've just rebased the PR here: #5645 Would be great if you could pick this work up and get it to the finish line |
1a4493a
to
ff30197
Compare
9ea19ea
to
7673256
Compare
See #5645 for future plans regarding Federation types improvements. |
Related #4722