Skip to content

Commit b946835

Browse files
smyrickShane Myrick
andauthored
Add bean factory example and docs (#657)
* Add bean factory example and docs * Consolidate nested arguments docs * Rename file Co-authored-by: Shane Myrick <[email protected]>
1 parent bb7324f commit b946835

File tree

5 files changed

+206
-50
lines changed

5 files changed

+206
-50
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
id: nested-fields
3+
title: Nested Fields
4+
---
5+
6+
There are a few ways in which you can access data in a query from different levels of arguments. Say we have the following schema:
7+
8+
```graphql
9+
type Query {
10+
findUser(name: String!): User
11+
}
12+
13+
type User {
14+
id: ID!
15+
photos(numberOfPhotos: Int!): [Photo!]!
16+
}
17+
18+
type Photo {
19+
url: String!
20+
}
21+
```
22+
23+
In Kotlin code, when we are in the `photos` function, if we want access to the parent field `findUser` and its
24+
arguments there are a couple ways we can access it:
25+
26+
27+
## DataFetchingEnvironment
28+
You can add the `DataFetchingEnvironment` as an argument which will allow you to view the entire query sent to the
29+
server. See more in the [DataFetchingEnvironment documentation](../execution/data-fetching-environment)
30+
31+
```kotlin
32+
class User {
33+
fun photos(environment: DataFetchingEnvironment, numberOfPhotos: Int): List<Photo> {
34+
val username = environment.executionStepInfo.parent.arguments["name"]
35+
return getPhotosFromDataSource(username, numberOfPhotos)
36+
}
37+
}
38+
```
39+
40+
## GraphQL Context
41+
You can add the `GraphQLContext` as an argument which will allow you to view the context object you set up in the
42+
data fetchers. See more in the [GraphQLContext documentation](../execution/contextual-data)
43+
44+
```kotlin
45+
class User {
46+
fun photos(context: MyContextObject, numberOfPhotos: Int): List<Photo> {
47+
val username = context.getDataFromMyCustomFunction()
48+
return getPhotosFromDataSource(username, numberOfPhotos)
49+
}
50+
}
51+
```
52+
53+
## Exluding from the Schema
54+
You can construct the child objects by passing down arguments as non-public fields or annotate the argument with [@GraphQLIgnore](../customizing-schemas/excluding-fields)
55+
56+
```kotlin
57+
class User(private val username: String) {
58+
59+
fun photosProperty(numberOfPhotos: Int): List<Photo> {
60+
return getPhotosFromDataSource(username, numberOfPhotos)
61+
}
62+
63+
fun photosIgnore(@GraphQLIgnore username: String, numberOfPhotos: Int): List<Photo> {
64+
return getPhotosFromDataSource(username, numberOfPhotos)
65+
}
66+
}
67+
```
68+
69+
## Spring BeanFactoryAware
70+
You can use Spring beans to wire different objects together at runtime.
71+
There is an example of how to set this up in the example app in [TopLevelBeanFactoryQuery.kt](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/spring/src/main/kotlin/com/expediagroup/graphql/examples/query/TopLevelBeanFactoryQuery.kt)
72+
73+
```kotlin
74+
@Component
75+
class UsersQuery : Query, BeanFactoryAware {
76+
private lateinit var beanFactory: BeanFactory
77+
78+
@GraphQLIgnore
79+
override fun setBeanFactory(beanFactory: BeanFactory) {
80+
this.beanFactory = beanFactory
81+
}
82+
83+
fun findUser(name: String): SubQuery = beanFactory.getBean(User::class.java, name)
84+
}
85+
86+
@Component
87+
@Scope("prototype")
88+
class User @Autowired(required = false) constructor(internal val userName: String) {
89+
90+
@Autowired
91+
private lateinit var service: PhotoService
92+
93+
fun photos(numberOfPhotos: Int): List<Photo> = service.findPhotos(userName, numberOfPhotos)
94+
}
95+
```
96+
97+
------
98+
99+
We have examples of these techniques implemented in Spring boot in the [example
100+
app](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/spring/src/main/kotlin/com/expediagroup/graphql/examples/query/NestedQueries.kt).

docs/writing-schemas/nested-queries.md

Lines changed: 0 additions & 49 deletions
This file was deleted.

examples/spring/src/main/kotlin/com/expediagroup/graphql/examples/hooks/CustomSchemaGeneratorHooks.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ import graphql.language.StringValue
2222
import graphql.schema.Coercing
2323
import graphql.schema.GraphQLScalarType
2424
import graphql.schema.GraphQLType
25+
import org.springframework.beans.factory.BeanFactoryAware
2526
import reactor.core.publisher.Mono
2627
import java.util.UUID
28+
import kotlin.reflect.KClass
2729
import kotlin.reflect.KType
30+
import kotlin.reflect.full.isSubclassOf
2831

2932
/**
3033
* Schema generator hook that adds additional scalar types.
@@ -46,6 +49,16 @@ class CustomSchemaGeneratorHooks(override val wiringFactory: KotlinDirectiveWiri
4649
Mono::class -> type.arguments.firstOrNull()?.type
4750
else -> type
4851
} ?: type
52+
53+
/**
54+
* Exclude the Spring bean factory interface
55+
*/
56+
override fun isValidSuperclass(kClass: KClass<*>): Boolean {
57+
return when {
58+
kClass.isSubclassOf(BeanFactoryAware::class) -> false
59+
else -> super.isValidSuperclass(kClass)
60+
}
61+
}
4962
}
5063

5164
internal val graphqlUUIDType = GraphQLScalarType.newScalar()
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2020 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.examples.query
18+
19+
import com.expediagroup.graphql.annotations.GraphQLIgnore
20+
import com.expediagroup.graphql.spring.operations.Query
21+
import org.springframework.beans.factory.BeanFactory
22+
import org.springframework.beans.factory.BeanFactoryAware
23+
import org.springframework.beans.factory.annotation.Autowired
24+
import org.springframework.context.annotation.Scope
25+
import org.springframework.stereotype.Component
26+
import org.springframework.stereotype.Service
27+
import kotlin.random.Random
28+
29+
/**
30+
* This is the top level query
31+
*/
32+
@Component
33+
class TopLevelBeanFactoryQuery : Query, BeanFactoryAware {
34+
35+
private lateinit var beanFactory: BeanFactory
36+
37+
@GraphQLIgnore
38+
override fun setBeanFactory(beanFactory: BeanFactory) {
39+
this.beanFactory = beanFactory
40+
}
41+
42+
fun topLevelBeanFactory(topLevelValue: String): SecondLevelBeanFactoryQuery = beanFactory.getBean(SecondLevelBeanFactoryQuery::class.java, topLevelValue)
43+
}
44+
45+
/**
46+
* This is the second level object that is created at execution time of the
47+
* [TopLevelBeanFactoryQuery.topLevelBeanFactory] function.
48+
*/
49+
@Component
50+
@Scope("prototype")
51+
class SecondLevelBeanFactoryQuery
52+
@Autowired(required = false)
53+
constructor(internal val topLevelValue: String) : BeanFactoryAware {
54+
55+
private lateinit var beanFactory: BeanFactory
56+
57+
@GraphQLIgnore
58+
override fun setBeanFactory(beanFactory: BeanFactory) {
59+
this.beanFactory = beanFactory
60+
}
61+
62+
fun secondLevel(secondLevelValue: String): ThirdLevelBeanFactoryQuery = beanFactory.getBean(ThirdLevelBeanFactoryQuery::class.java, topLevelValue, secondLevelValue)
63+
}
64+
65+
/**
66+
* This is the third level object that is created at execution time of the
67+
* [SecondLevelBeanFactoryQuery.secondLevel] function.
68+
*/
69+
@Component
70+
@Scope("prototype")
71+
class ThirdLevelBeanFactoryQuery
72+
@Autowired(required = false)
73+
constructor(
74+
internal val topLevelValue: String,
75+
internal val secondLevelValue: String
76+
) {
77+
78+
@Autowired
79+
private lateinit var service: ExampleExternalService
80+
81+
fun printMessage(thirdLevelValue: String): String =
82+
"topLevelValue=$topLevelValue, secondLevelValue=$secondLevelValue, thirdLevelValue=$thirdLevelValue, serviceValue=${service.getData()}"
83+
}
84+
85+
/**
86+
* Spring service that represents some other component that is not
87+
* created from the bean factory
88+
*/
89+
@Service
90+
class ExampleExternalService {
91+
fun getData(): Int = Random.nextInt()
92+
}

website/sidebars.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"writing-schemas/lists",
1818
"writing-schemas/interfaces",
1919
"writing-schemas/unions",
20-
"writing-schemas/nested-queries",
20+
"writing-schemas/nested-fields",
2121
"writing-schemas/annotations"
2222
]
2323
},

0 commit comments

Comments
 (0)