-
Notifications
You must be signed in to change notification settings - Fork 7
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
Federation: support custom directives in federated schemas #145
Comments
If Should be |
@ausov From what I can tell, there's some inconsistency in how directives are handled in this module. The filtered directives in the line you're referencing are used to print the directive declarations like However, in functions like So custom schema directives are declared, but never included where they're used! |
@lennyburdette thanks for bringing this discussion over to GitHub! As @jbaxleyiii mentioned in the original thread, we have some questions to think through about service and gateway level directives. We would love to hear ideas from the community while we flesh out our understanding of the problem and what the right solution might be. Providing use cases will help us to drive patterns as we design support for this. Thanks for opening up the discussion 😄 |
@trevor-scheer In my case I was using type Query {
someQuery: Result
someOtherQuery(id: ID!): Result @auth(roles: [ADMIN, MODERATOR])
}
type Mutation {
someMutation(id: ID!): Result @auth(roles: [ADMIN, MODERATOR])
} Any thoughts about such drive pattern? |
Here's another use case. I'm talking to a team that's concerned about restricting access to slow/brittle endpoints. They currently use service-to-service ACLs and rate-limiting, but in a federated graph, all requests would come from the same service (the gateway.) I'm thinking the right proposal would be client-based rate limiting. It'd be great to declare rate limit rules on the fields. extend type Query {
slowSearchField(term: String!): [Thing] @rateLimit(qps: 10)
}
extend type Parent {
poorlyIndexedField: [Result] @rateLimitRule(name: "key.for.rule.in.another.system")
} If the gateway could analyze the rate limiting directives and drop fields or whole requests to the federated schemas would be 💯 |
Can we just allow schema directives to be created and if a custom directive is trying to replace a federation directive just throw an error, unless you are extending one of the federation directives? edit: I need this functionality sooner rather than later so just throwing out some ideas |
@trevor-scheer what if directives will be excluded only from |
We started implementing federation for our service and hit this exact roadblock. |
Is there a rough ETA on when we could expect seeing custom directive support? We are about to start a microservice oriented graphQL system and the lack of directive support is a roadblock for us, which is unfortunate as federated schemas tick so many boxes for us. Having a rough indication of when we could expect directive support - i.e. weeks or six months would greatly help in our architecture choices 🙏 |
Our use case is to leverage custom directives to wrap either some or all resolvers fields in an object with some authorization code that implements access control and decides whether the original resolver will be run. The directive @authorize (
requires: ResourcePrivilege = INTERNAL_OPERATIONS_PRIVILEGE,
) on OBJECT | FIELD_DEFINITION
type Query @authorize(requires: ACCOUNT_OPERATIONS_PRIVILEGE) {
users: [User]
customers: [Customer]
}
type Mutation @authorize(requires: ACCOUNT_OPERATIONS_PRIVILEGE) {
addCustomerUser(customerId: Int!, input: AddCustomerUserInput! ): AddCustomerUserResult
deleteCustomer(customerId: Int! ): String @authorize(requires: SYSTEM_OPERATIONS_PRIVILEGE)
} When I attempt to convert our service to Apollo Federation, and import a DocumentNode (using api_server/node_modules/graphql/validation/rules/KnownDirectives.js:86
throw _iteratorError2;
^
TypeError: Cannot read property 'kind' of undefined
at KnownDirectives (api_server/node_modules/graphql/validation/rules/KnownDirectives.js:70:15)
at api_server/node_modules/graphql/validation/validate.js:63:12
at Array.map (<anonymous>)
at Object.validateSDL (api_server/node_modules/graphql/validation/validate.js:62:24)
at Object.buildSchemaFromSDL (api_server/node_modules/apollo-graphql/src/schema/buildSchemaFromSDL.ts:69:18)
at buildFederatedSchema (api_server/node_modules/@apollo/federation/src/service/buildFederatedSchema.ts:30:16)
at Object.<anonymous> (api_server/api/schema.external.js:47:40)
at Module._compile (internal/modules/cjs/loader.js:688:30)
at Module._compile (api_server/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:699:10)
at Object.newLoader [as .js] (api_server/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:598:32)
at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
at Function.Module._load (internal/modules/cjs/loader.js:529:3)
at Module.require (internal/modules/cjs/loader.js:636:17)
at require (internal/modules/cjs/helpers.js:20:18) I also tried defining my DocumentNode using a gql-tagged string instead: typeDefs: gql`
directive @audit on OBJECT | FIELD_DEFINITION
type Query @audit {
test: String
}
`,
resolvers: {
Query: {
test: () => 'Hello, World!',
},
}, and passed the directives and the federated schema to const apiServer = new ApolloServer({
schema: testFederatedSchema,
schemaDirectives,
}) , and while this ran my resolver without an error, it did not run the custom directive. Lack of support for custom directives in the upstream server means converting our current service into a federated service (that can be used by a gateway) will require a decent amount of work (since it would require changing our authorization implementation). While I don't think custom directives need to live at the gateway level (see https://medium.com/@__xuorig__/the-rise-of-graphql-overambitious-api-gateways-2c6f054e80a1), I do think they need to continue working at the individual upstream services if those services were already using directives, and we convert them to be usable by a gateway. |
Thanks everyone for all of your suggestions, use cases, and general input - it's much appreciated! Going forward, we plan to support schema directives for individual services in the future, but we do not have any plans to add this support at the gateway level. Though not listed on the issue itself, this work is part of the Apollo Server 3.0 roadmap. Schema construction is undergoing changes as we move away from For those asking about timeframe, I can only speculate that it will be on the order of months. The work for AS3 is a top priority, however we have a lot to accomplish. There will be a time where we would (gladly!) accept and provide guidance for a contribution from the community, but we're not quite there yet. Thanks everyone for your participation and patience - I know this is exciting stuff 😄 |
for those who cannot get directives running at federated service level, you can use mergeSchemas :
|
Is this what are you looking for? It was released with |
Closing this, as executable directives are now supported via apollographql/apollo-server#3464 and service-level type system directives are permitted via apollographql/apollo-server#3536. |
apollographql/apollo-server#3736 makes the gateway tolerate usages of type system directives in service schemas. However, federated services still don't return these usages from |
For all the interested parties: there is no issue using schema (aka Type System) directives on the federated services side except the inconvenient API and knotty internals of all the GraphQL tooling.
// That won't enable schema directives
const server = new ApolloServer({
schema: buildFederatedSchema([{ typeDefs, resolvers }]),
schemaDirectives,
}); So, we should attach them directly to the schema. Thanks @lethot, he found a workaround with A better and conventional way is to use Here is an example: // That enables schema directives on the federated service side
const schema = buildFederatedSchema([{ typeDefs, resolvers }])
SchemaDirectiveVisitor.visitSchemaDirectives(schema, schemaDirectives);
const server = new ApolloServer({
schema: schema,
}) |
Should we add schema directive to every federated schema (service) even if the service does not need the schema directive ? |
This still does not appear to be working on input types as per @sponomarev suggestion. The schema indeed gets modified to show the name of the custom directive. I can set breakpoints and even see that the custom type instantiated by the |
@ilmimris No, you don't. Schema directives transform types, fields, etc. The affect only the current service. |
@robross0606 Sorry, can't say anything specific without seeing the code you're talking about. |
@sponomarev , I'm attempting to tie in the apollo-server-constraint-directive library to our service which is federated. The basic steps are as indicated in their README, except instead of calling
There are no errors from this, and the resulting schema looks for all the world like it should work. Schema introspection shows the correct types as per the library in places where I've added constraints. So, for example, if add an
The constructor for StringType looks like this:
So, I can set breakpoints in debug mode and see that the correct type is created and "wraps" the original type, and I can see that the GraphQL type in introspection becomes "ValidatedString". However, making a call to the GraphQL never appears to pass through the I'm baffled as to why. |
@robross0606 We're also using Federation and faced the same issue when I tried to add validation with |
@makaivelli, I did not end up using Here's a simplified example of how I ended up using it: const { mergeTypeDefs, mergeResolvers } = require('@graphql-tools/merge')
const { constraintDirective, constraintDirectiveTypeDefs } = require('graphql-constraint-directive')
const schemaTypeDefs = require('./schema')
const allResolvers= require('./resolvers')
const directiveTransform = constraintDirective()
const directiveTypeDef = schemaDef: gql`${constraintDirectiveTypeDefs}`
const allTypeDefs = mergeTypeDefs([{ schemaDefs: directiveTypeDef }, schemaTypeDefs], { all: true })
const typeDefsAndResolvers = {
typeDefs: allTypeDefs,
resolvers: allResolvers
}
let federatedSchema = buildFederatedSchema([typeDefsAndResolvers])
federatedSchema = directiveTransform(federatedSchema)
module.exports = {
federatedSchema,
schemaDirectives
} So, there are two parts to the directive: the directive typedefs and the directive implementation/handlers. The implementation is the bizarre part since it is "applied" by calling Not sure if this is the only (or even correct) way to use it, but it appears to work for us. |
Is there a way to integrate the https://github.com/teamplanes/graphql-rate-limit at apollo/gateway level? |
In
@apollo/[email protected]
, the printSchema function throws away SDL directives that aren't@deprecated/@key/@external/@extends/@requires/@provides
.This prevents the federation gateway from knowing about directives via the
_Service.sdl
field.Conveying custom directives from federated schemas to the gateway is the first step to supporting centralized logic in the gateway for functionality like unified authorization.
Thanks for the hard work!
The text was updated successfully, but these errors were encountered: