-
Notifications
You must be signed in to change notification settings - Fork 66
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
Add Federation Support #98
Add Federation Support #98
Conversation
@NeedleInAJayStack is directive support already in Graphiti? |
Not at the moment, but it's deinitely a highly sought-after feature I should say, just for clarity, that the GraphQL standard directives 'skip' and 'include' are supported, but custom directives like Apollo Federation relies on are not supported today. |
I can work on that feature - would you prefer directive support before merging in federation? |
Awesome, thank you so much @samisuteria! Yeah, from my understanding federation support typically uses custom directives, so solving custom directives in a general way first makes sense to me. Thanks again, and great work so far! |
Federation can work without directives in the application code. If you are writing schema first (I'm not sure the split between schema-first and code-first in the GraphQL ecosystem) you can enable federation without directives. As long as the service sdl query returns the schema federation will work correctly. Even Apollo's reference server avoids using directives in the code and just loads the schema from the file system here.
If you check out the
I still plan on working on adding directive/sdl support for Graphiti but I think this PR is ready for review and can be used by people who are schema-first. |
Sounds good for me. Nice work 👍 . While It's probably a bit awkward atm given you can't really fully do schema-first or use directive with Graphiti, those feature can be separated and worked on later with future PR / releases. |
Great info @samisuteria! Thanks for walking me through that, and in that case I'm totally ok with this going in before directive support. Sorry about the delay - I'll get a review in this weekend. |
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.
Overall, I like the direction a lot. However, I think there may be some opportunities to align this more with the existing patterns (similar to how Connection
was added). Things like integrating Key
definition directly into the schema DSL instead of using a resolver protocol, and creating relevant GraphQL types during the SchemaBuilder.build
phase.
If you don't mind, I've done some scratch work on the tip of your branch to make sure some of these ideas work out, and I'd like to see if the changes are acceptable to you. I'll post a link in a bit.
} | ||
|
||
public extension FederationEntity { | ||
static var typename: String { Reflection.name(for: Self.self) } |
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.
Is this 'self' name reflection compatible with types that we have aliased in our GraphQL schema? For example:
Type(LocationObject.self, as: "Location") {
Field("id", at: \.id)
Field("name", at: \.name)
}
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 don't think it's compatible with the one in the schema. I just provided a default implementation for simplicity but the user can override it if needed. I did that here since there was a User type already defined in another test. https://github.com/GraphQLSwift/Graphiti/pull/98/files#diff-7086f136baa260fe52dc5fa6c66acf11b265a563f76708d829293b38aa10937cR84
associatedtype Context | ||
static var encoder: JSONEncoder { get } | ||
static var decoder: JSONDecoder { get } | ||
var sdl: String { get } | ||
func entity(context: Context, key: FederationEntityKey, group: EventLoopGroup) -> EventLoopFuture<FederationEntity?> |
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 resolver protocol duplicates quite a bit of structure from the Schema type itself (things like coders and generic Context). Also, I'm a little wary to require protocol conformance on our resolver types, since that's not something we've done in the past.
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 agree and was not a fan of it.
extension SchemaBuilder where Resolver: FederationResolver, Resolver.Context == Context { | ||
@discardableResult | ||
/// Enable federation to add additional capabilities for federation subgraph support | ||
public func enableFederation() -> Self { | ||
let federationSchema = PartialSchema<Resolver, Context>( |
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.
Could we do this in a similar way to the Apollo Connection
support where we handle it during the schema creation itself?
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 didn't see the Connection
code but looking at it now - it really does seem the way to go.
associatedtype Context | ||
static var encoder: JSONEncoder { get } | ||
static var decoder: JSONDecoder { get } | ||
var sdl: String { get } |
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.
For this parameter, we need to define our SDL string separately and ensure it corresponds to the schema definition we coded, right? It would be awesome if we could generate the SDL automatically from the defined schema, but I know that's probably a bigger task.
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.
Yea I would really prefer to not require the SDL since that requires users to put federation specific code into their schema. Maybe have a way of verifying the the supplied SDL to the built schema to make sure all types/fields/keys are included?
static let entityKeys: [(entity: FederationEntity.Type, keys: [FederationEntityKey.Type])] = [ | ||
(Product.self, [Product.EntityKey1.self, Product.EntityKey2.self, Product.EntityKey3.self]), | ||
(DeprecatedProduct.self, [DeprecatedProduct.EntityKey.self]), | ||
(ProductResearch.self, [ProductResearch.EntityKey.self]), | ||
(ProductUser.self, [ProductUser.EntityKey.self]), | ||
] |
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.
We map up our Entities to Keys through the resolver already - is there any way we could reuse that definition instead of requiring it twice?
@samisuteria See what you think of this twist on your implementation: https://github.com/NeedleInAJayStack/Graphiti/tree/federation_jay If you're okay with it, we can pull those commits into this MR. |
@NeedleInAJayStack I really like the changes on your branch and the The syntax for defining keys in the schema seems a bit complicated (anytime you have multiple builder closures in a function). What do you think of something like this? It would just require
Eventually when the library can generate the SDL itself, I imagine other directives would work the same way and the syntax would be similar to SwiftUI's ViewModifier |
@samisuteria Ooo, I love that idea. You're right, it looks cleaner and better preserves the public API |
@samisuteria I just implemented your suggestion on my branch. If you like it, feel free to pull those commits into this MR. Thanks again! |
Just pulled in the changes - thank you so much for the help on this :) |
This is a useful dockerfile if you're on Apple Silicon and want to test older versions of Swift:
|
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.
Thanks for fixing the CI @samisuteria!
A basic implementation of federation. Some issues I have and could use guidance:
Using AnyCodable as a dependencyUsed the Map type from GraphQLWhere to store JSONEncoder/Decoder for reusabilityUse schema codersUsers having to implementNo custom protocol for federation resolver. Users still need to copy the SDL in directlyFederationContext/FederationResolver andHow to apply directives to fields/types (ie:Key has been implemented on Type and maps to a function in the resolver.@key(fields: "id")
,@inaccessible
,@shareable
)Supporting older versions of Swift (any FederationContext
requires 5.7)UpdatedNo longer neededFederationResolver
protocol to offer a method for older versions of Swift