-
Notifications
You must be signed in to change notification settings - Fork 362
Add docs for new schema generator feature #594
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
Merged
dariuszkuc
merged 1 commit into
ExpediaGroup:master
from
smyrick:document-new-schema-generator
Feb 7, 2020
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
--- | ||
id: advanced-features | ||
title: Advanced Features | ||
--- | ||
|
||
## Adding Custom Additional Types | ||
|
||
There are a couple ways you can add more types to the schema without having them be directly consumed by a type in your schema. | ||
This may be required for [Apollo Federation](federated/apollo-federation), or maybe adding other interface implementations that are not picked up. | ||
|
||
|
||
### `SchemaGenerator::addAdditionalTypesWithAnnotation` | ||
|
||
This method is protected so if you override the `SchemaGenerator` used you can call this method to add types that have a specific annotation. | ||
You can see how this is used in `graphql-kotlin-federation` as [an example](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/federation/FederatedSchemaGenerator.kt). | ||
|
||
### `SchemaGenerator::generateAdditionalTypes` | ||
|
||
This method is called by `SchemaGenerator::get` after all the queries, mutations, and subscriptions have been generated and it is going to add all the types saved in an internal set that were not generated by reflection. | ||
To change the behaviour, you can update the set and then call the super method with the new value. | ||
|
||
Example: | ||
|
||
```kotlin | ||
class CustomSchemaGenerator(config: SchemaGeneratorConfig) : SchemaGenerator(config) { | ||
|
||
override fun generateAdditionalTypes(types: Set<KType>): Set<GraphQLType> { | ||
val newTypes = types.toMutableSet().add(MyNewType()::class.createType()) | ||
return super.generateAdditionalTypes(newTypes) | ||
} | ||
} | ||
``` |
4 changes: 2 additions & 2 deletions
4
docs/customizing-schemas/evolving-schema.md → ...customizing-schemas/deprecating-schema.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,155 +1,155 @@ | ||
--- | ||
id: directives | ||
title: Directives | ||
--- | ||
GraphQL directives can be used to transform the schema types, fields and arguments as well as modify the runtime | ||
behavior of the query (e.g. implement access control, etc). Common use cases involve limiting functionality based on the | ||
user authentication and authorization. While [GraphQL | ||
spec](https://graphql.github.io/graphql-spec/draft/#sec-Type-System.Directives) specifies two types of directives - | ||
`executable` (aka query) and `type system` (aka schema) directives, only the latter one is supported by | ||
`graphql-kotlin-schema-generator`. | ||
## Default Directives | ||
`@deprecated` - schema directive used to represent deprecated portion of the schema. | ||
See [@Deprecated](customizing-schemas/evolving-schema) annotation documentation for more details | ||
```graphql | ||
type Query { | ||
deprecatedQuery: Boolean! @deprecated(reason: "No longer supported") | ||
} | ||
``` | ||
`@skip` - query directive that allows for conditional exclusion of fields or fragments | ||
```graphql | ||
query myQuery($shouldSkip: Boolean) { | ||
myField @skip(if: $shouldSkip) | ||
} | ||
``` | ||
`@include` - query directive that allows for conditional inclusion of fields or fragments | ||
```graphql | ||
query myQuery($shouldInclude: Boolean) { | ||
myField @include(if: $shouldInclude) | ||
} | ||
``` | ||
## Custom Directives | ||
Custom directives can be added to the schema using custom annotations: | ||
```kotlin | ||
@GraphQLDirective( | ||
name = "awesome", | ||
description = "This element is great", | ||
locations = [FIELD_DEFINITION] | ||
) | ||
annotation class AwesomeDirective(val value: String) | ||
class MyQuery { | ||
@AwesomeDirective("cool stuff") | ||
val somethingGreat: String = "Hello World" | ||
} | ||
``` | ||
The directive will then added to the schema as: | ||
```graphql | ||
# This element is great | ||
directive @awesome(value: String) on FIELD_DEFINITION | ||
type MyQuery { | ||
somethingGreat: String @awesome("cool stuff") | ||
} | ||
``` | ||
Directives can be added to various places in the schema. See the | ||
[graphql.introspection.Introspection.DirectiveLocation](https://github.com/graphql-java/graphql-java/blob/v13.0/src/main/java/graphql/introspection/Introspection.java#L332) | ||
enum from `graphql-java` for a full list of valid locations. | ||
**Note that GraphQL directives are currently not available through introspection and you have to use SDL directly | ||
instead (you can use convenient `print` extension function of `GraphQLSchema`)**. See [GraphQL | ||
issue](https://github.com/facebook/graphql/issues/300) and corresponding [graphql-java | ||
issue](https://github.com/graphql-java/graphql-java/issues/1017) for more details about the introspection issue. | ||
### Naming Convention | ||
As described in the example above, the directive name in the schema will by default come from the | ||
`@GraphQLDirective.name` attribute which should follow `lowerCamelCase` format. If this value is not specified, the | ||
directive name will default to the normalized decapitalized name of the annotated annotation (eg: `awesomeDirective` in | ||
the example above). | ||
### Customizing Behavior | ||
Directives allow you to customize the behavior of your schema based on some predefined conditions. Simplest way to | ||
modify the default behavior of your GraphQLTypes is by providing your custom `KotlinSchemaDirectiveWiring` through | ||
`KotlinDirectiveWiringFactory` factory used by your `SchemaGeneratorHooks`. | ||
Example of a directive that converts field to lowercase | ||
```kotlin | ||
@GraphQLDirective(name = "lowercase", description = "Modifies the string field to lowercase") | ||
annotation class LowercaseDirective | ||
class LowercaseSchemaDirectiveWiring : KotlinSchemaDirectiveWiring { | ||
override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition { | ||
val field = environment.element | ||
val originalDataFetcher: DataFetcher<Any> = environment.getDataFetcher() | ||
val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher( | ||
originalDataFetcher, | ||
BiFunction<DataFetchingEnvironment, Any, Any>{ _, value -> value.toString().toLowerCase() } | ||
) | ||
environment.setDataFetcher(lowerCaseFetcher) | ||
return field | ||
} | ||
} | ||
``` | ||
While you can manually apply all the runtime wirings to the corresponding GraphQL types directly in | ||
`SchemaGeneratorHooks#onRewireGraphQLType`, we recommend the usage of our | ||
[KotlinDirectiveWiringFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/directives/KotlinDirectiveWiringFactory.kt) | ||
to simplify the integrations. `KotlinDirectiveWiringFactory` accepts a mapping of directives to corresponding wirings or | ||
could be extended to provide the wirings through `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring` that accepts | ||
`KotlinSchemaDirectiveEnvironment`. | ||
```kotlin | ||
val queries = ... | ||
val customWiringFactory = KotlinDirectiveWiringFactory( | ||
manualWiring = mapOf<String, KotlinSchemaDirectiveWiring>("lowercase" to LowercaseSchemaDirectiveWiring())) | ||
val customHooks = object : SchemaGeneratorHooks { | ||
override val wiringFactory: KotlinDirectiveWiringFactory | ||
get() = customWiringFactory | ||
} | ||
val schemaGeneratorConfig = SchemaGeneratorConfig(hooks = customHooks) | ||
val schema = toSchema(queries = queries, config = schemaGeneratorConfig) | ||
``` | ||
While providing directives on different schema elements you will be able to modify the underlying GraphQL types. Keep in | ||
mind though that data fetchers are used to resolve the fields so only field directives (and by association their | ||
arguments directives) can modify runtime behavior based on the context and user input. | ||
**NOTE: `graphql-kotlin` prioritizes manual wiring mappings over the wirings provided by the | ||
`KotlinDirectiveWiringFactory#getSchemaDirectiveWiring`. This is a different behavior than `graphql-java` which will | ||
first attempt to use `WiringFactory` and then fallback to manual overrides.** | ||
For more details please refer to the example usage of directives in our [example | ||
app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/spring). | ||
## Directive Chaining | ||
Directives are applied in the order annotations are declared on the given object. Given | ||
```kotlin | ||
@Directive1 | ||
@Directive2 | ||
fun doSomething(): String { | ||
// does something | ||
} | ||
``` | ||
`Directive1` will be applied first followed by the `Directive2`. | ||
--- | ||
id: directives | ||
title: Directives | ||
--- | ||
|
||
GraphQL directives can be used to transform the schema types, fields and arguments as well as modify the runtime | ||
behavior of the query (e.g. implement access control, etc). Common use cases involve limiting functionality based on the | ||
user authentication and authorization. While [GraphQL | ||
spec](https://graphql.github.io/graphql-spec/draft/#sec-Type-System.Directives) specifies two types of directives - | ||
`executable` (aka query) and `type system` (aka schema) directives, only the latter one is supported by | ||
`graphql-kotlin-schema-generator`. | ||
|
||
## Default Directives | ||
|
||
`@deprecated` - schema directive used to represent deprecated portion of the schema. | ||
See [@Deprecated](customizing-schemas/deprecating-schema) annotation documentation for more details | ||
|
||
```graphql | ||
type Query { | ||
deprecatedQuery: Boolean! @deprecated(reason: "No longer supported") | ||
} | ||
``` | ||
|
||
`@skip` - query directive that allows for conditional exclusion of fields or fragments | ||
|
||
```graphql | ||
query myQuery($shouldSkip: Boolean) { | ||
myField @skip(if: $shouldSkip) | ||
} | ||
``` | ||
|
||
`@include` - query directive that allows for conditional inclusion of fields or fragments | ||
|
||
```graphql | ||
query myQuery($shouldInclude: Boolean) { | ||
myField @include(if: $shouldInclude) | ||
} | ||
``` | ||
|
||
## Custom Directives | ||
|
||
Custom directives can be added to the schema using custom annotations: | ||
|
||
```kotlin | ||
@GraphQLDirective( | ||
name = "awesome", | ||
description = "This element is great", | ||
locations = [FIELD_DEFINITION] | ||
) | ||
annotation class AwesomeDirective(val value: String) | ||
|
||
class MyQuery { | ||
@AwesomeDirective("cool stuff") | ||
val somethingGreat: String = "Hello World" | ||
} | ||
``` | ||
|
||
The directive will then added to the schema as: | ||
|
||
```graphql | ||
# This element is great | ||
directive @awesome(value: String) on FIELD_DEFINITION | ||
|
||
type MyQuery { | ||
somethingGreat: String @awesome("cool stuff") | ||
} | ||
``` | ||
|
||
Directives can be added to various places in the schema. See the | ||
[graphql.introspection.Introspection.DirectiveLocation](https://github.com/graphql-java/graphql-java/blob/v13.0/src/main/java/graphql/introspection/Introspection.java#L332) | ||
enum from `graphql-java` for a full list of valid locations. | ||
|
||
**Note that GraphQL directives are currently not available through introspection and you have to use SDL directly | ||
instead (you can use convenient `print` extension function of `GraphQLSchema`)**. See [GraphQL | ||
issue](https://github.com/facebook/graphql/issues/300) and corresponding [graphql-java | ||
issue](https://github.com/graphql-java/graphql-java/issues/1017) for more details about the introspection issue. | ||
|
||
### Naming Convention | ||
|
||
As described in the example above, the directive name in the schema will by default come from the | ||
`@GraphQLDirective.name` attribute which should follow `lowerCamelCase` format. If this value is not specified, the | ||
directive name will default to the normalized decapitalized name of the annotated annotation (eg: `awesomeDirective` in | ||
the example above). | ||
|
||
### Customizing Behavior | ||
|
||
Directives allow you to customize the behavior of your schema based on some predefined conditions. Simplest way to | ||
modify the default behavior of your GraphQLTypes is by providing your custom `KotlinSchemaDirectiveWiring` through | ||
`KotlinDirectiveWiringFactory` factory used by your `SchemaGeneratorHooks`. | ||
|
||
Example of a directive that converts field to lowercase | ||
|
||
```kotlin | ||
@GraphQLDirective(name = "lowercase", description = "Modifies the string field to lowercase") | ||
annotation class LowercaseDirective | ||
|
||
class LowercaseSchemaDirectiveWiring : KotlinSchemaDirectiveWiring { | ||
|
||
override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition { | ||
val field = environment.element | ||
val originalDataFetcher: DataFetcher<Any> = environment.getDataFetcher() | ||
|
||
val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher( | ||
originalDataFetcher, | ||
BiFunction<DataFetchingEnvironment, Any, Any>{ _, value -> value.toString().toLowerCase() } | ||
) | ||
environment.setDataFetcher(lowerCaseFetcher) | ||
return field | ||
} | ||
} | ||
``` | ||
|
||
While you can manually apply all the runtime wirings to the corresponding GraphQL types directly in | ||
`SchemaGeneratorHooks#onRewireGraphQLType`, we recommend the usage of our | ||
[KotlinDirectiveWiringFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/directives/KotlinDirectiveWiringFactory.kt) | ||
to simplify the integrations. `KotlinDirectiveWiringFactory` accepts a mapping of directives to corresponding wirings or | ||
could be extended to provide the wirings through `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring` that accepts | ||
`KotlinSchemaDirectiveEnvironment`. | ||
|
||
```kotlin | ||
val queries = ... | ||
val customWiringFactory = KotlinDirectiveWiringFactory( | ||
manualWiring = mapOf<String, KotlinSchemaDirectiveWiring>("lowercase" to LowercaseSchemaDirectiveWiring())) | ||
val customHooks = object : SchemaGeneratorHooks { | ||
override val wiringFactory: KotlinDirectiveWiringFactory | ||
get() = customWiringFactory | ||
} | ||
val schemaGeneratorConfig = SchemaGeneratorConfig(hooks = customHooks) | ||
val schema = toSchema(queries = queries, config = schemaGeneratorConfig) | ||
``` | ||
|
||
While providing directives on different schema elements you will be able to modify the underlying GraphQL types. Keep in | ||
mind though that data fetchers are used to resolve the fields so only field directives (and by association their | ||
arguments directives) can modify runtime behavior based on the context and user input. | ||
|
||
**NOTE: `graphql-kotlin` prioritizes manual wiring mappings over the wirings provided by the | ||
`KotlinDirectiveWiringFactory#getSchemaDirectiveWiring`. This is a different behavior than `graphql-java` which will | ||
first attempt to use `WiringFactory` and then fallback to manual overrides.** | ||
|
||
For more details please refer to the example usage of directives in our [example | ||
app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/spring). | ||
|
||
## Directive Chaining | ||
|
||
Directives are applied in the order annotations are declared on the given object. Given | ||
|
||
```kotlin | ||
@Directive1 | ||
@Directive2 | ||
fun doSomething(): String { | ||
// does something | ||
} | ||
``` | ||
|
||
`Directive1` will be applied first followed by the `Directive2`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
lets keep it as more generic evolving schema and add notes about adding new fields etc
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 was mainly doing this to help the sidebar be a little more clear. Right now it is not super descriptive for what is there.
Do you think we can just include this all on one page?
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.
The only info in the topic right now is on deprecating schema elements, so I'd recomment the title: Deprecating Schema Elements.
If or when you talk about other ways to evolve the schema or its elements, I'd change to something like Evolving Schema.
Just my two cents.