Skip to content

Commit b146294

Browse files
authored
Add new example for spring-boot-dgs (#227)
This example shows, how a type can be extended with a custom data fetcher. It utilises Netflix Domain Graph Service (DGS).
1 parent 329a7d7 commit b146294

File tree

13 files changed

+489
-3
lines changed

13 files changed

+489
-3
lines changed

examples/dgs-spring-boot/pom.xml

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.neo4j</groupId>
7+
<artifactId>neo4j-graphql-java-examples</artifactId>
8+
<version>1.3.1-SNAPSHOT</version>
9+
</parent>
10+
11+
<groupId>org.neo4j.graphql.examples</groupId>
12+
<artifactId>dgs-spring-boot</artifactId>
13+
14+
<name>Example - dgs-spring-boot</name>
15+
<description>Example for using neo4j-graphql-java with Spring Boot and Netflix Domain Graph Service (DGS)</description>
16+
17+
<properties>
18+
<testcontainers.version>1.15.3</testcontainers.version>
19+
<spring-boot.version>2.3.10.RELEASE</spring-boot.version>
20+
</properties>
21+
22+
<dependencies>
23+
<!-- spring dependencies -->
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-configuration-processor</artifactId>
27+
<optional>true</optional>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.springframework.boot</groupId>
31+
<artifactId>spring-boot-starter-web</artifactId>
32+
</dependency>
33+
34+
<!-- neo4j driver + the neo4j-graphql-java library -->
35+
<dependency>
36+
<groupId>org.neo4j.driver</groupId>
37+
<artifactId>neo4j-java-driver-spring-boot-starter</artifactId>
38+
<version>4.2.4.0</version>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.neo4j</groupId>
42+
<artifactId>neo4j-graphql-java</artifactId>
43+
<version>1.3.1-SNAPSHOT</version>
44+
</dependency>
45+
46+
<dependency>
47+
<groupId>com.netflix.graphql.dgs</groupId>
48+
<artifactId>graphql-dgs-spring-boot-starter</artifactId>
49+
<version>3.12.1</version>
50+
</dependency>
51+
52+
<!-- Kotlin dependencies -->
53+
<dependency>
54+
<groupId>com.fasterxml.jackson.module</groupId>
55+
<artifactId>jackson-module-kotlin</artifactId>
56+
</dependency>
57+
<dependency>
58+
<groupId>org.jetbrains.kotlin</groupId>
59+
<artifactId>kotlin-reflect</artifactId>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.jetbrains.kotlin</groupId>
63+
<artifactId>kotlin-stdlib-jdk8</artifactId>
64+
</dependency>
65+
66+
<!-- Test dependencies -->
67+
<dependency>
68+
<groupId>org.springframework.boot</groupId>
69+
<artifactId>spring-boot-starter-test</artifactId>
70+
<scope>test</scope>
71+
</dependency>
72+
<dependency>
73+
<groupId>org.testcontainers</groupId>
74+
<artifactId>neo4j</artifactId>
75+
<version>${testcontainers.version}</version>
76+
<scope>test</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>org.testcontainers</groupId>
80+
<artifactId>junit-jupiter</artifactId>
81+
<version>${testcontainers.version}</version>
82+
<scope>test</scope>
83+
</dependency>
84+
</dependencies>
85+
86+
<dependencyManagement>
87+
<dependencies>
88+
<dependency>
89+
<groupId>org.springframework.boot</groupId>
90+
<artifactId>spring-boot-dependencies</artifactId>
91+
<version>${spring-boot.version}</version>
92+
<type>pom</type>
93+
<scope>import</scope>
94+
</dependency>
95+
</dependencies>
96+
</dependencyManagement>
97+
98+
<build>
99+
<plugins>
100+
<plugin>
101+
<groupId>org.springframework.boot</groupId>
102+
<artifactId>spring-boot-maven-plugin</artifactId>
103+
</plugin>
104+
105+
<plugin>
106+
<groupId>io.github.deweyjose</groupId>
107+
<artifactId>graphqlcodegen-maven-plugin</artifactId>
108+
<version>1.8</version>
109+
<executions>
110+
<execution>
111+
<goals>
112+
<goal>generate</goal>
113+
</goals>
114+
</execution>
115+
</executions>
116+
<configuration>
117+
<schemaPaths>
118+
<param>src/main/resources/schema/schema.graphqls</param>
119+
</schemaPaths>
120+
<generateClient>true</generateClient>
121+
<generateInterfaces>true</generateInterfaces>
122+
<generateDataTypes>true</generateDataTypes>
123+
<packageName>org.neo4j.graphql.examples.dgsspringboot.types</packageName>
124+
</configuration>
125+
</plugin>
126+
<plugin>
127+
<groupId>org.codehaus.mojo</groupId>
128+
<artifactId>build-helper-maven-plugin</artifactId>
129+
<executions>
130+
<execution>
131+
<phase>generate-sources</phase>
132+
<goals>
133+
<goal>add-source</goal>
134+
</goals>
135+
<configuration>
136+
<sources>
137+
<source>${project.build.directory}/generated-sources</source>
138+
</sources>
139+
</configuration>
140+
</execution>
141+
</executions>
142+
</plugin>
143+
</plugins>
144+
</build>
145+
</project>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
= Example: Integration of Neo4j-GraphQL-Java into a Spring Boot application in combination with Netflix DGS
2+
3+
== Overview
4+
5+
This example uses the https://netflix.github.io/dgs/[Netflix DGS Framework (Domain Graph Service)]
6+
7+
In the link:src/main/kotlin/org/neo4j/graphql/examples/dgsspringboot/config/Neo4jConfiguration.kt[Neo4jConfiguration]
8+
a DataFetchingInterceptor is created, which will be bound to all fields augmented by the neo4j-graphql-library.
9+
Its purpose is the execution of the cypher query and the transformation of the query result.
10+
11+
In the link:src/main/kotlin/org/neo4j/graphql/examples/dgsspringboot/config/GraphQLConfiguration.kt[GraphQLConfiguration]
12+
the type definitions of link:src/main/resources/neo4j.graphql[schema] are loaded and augmented.
13+
14+
In this example some fields of the enhanced type (neo4j) are extended with
15+
link:src/main/kotlin/org/neo4j/graphql/examples/dgsspringboot/datafetcher/AdditionalDataFetcher.kt[custom data fetcher] whose link:src/main/resources/schema/schema.graphqls[schema is separately defined].
16+
17+
With This in place you can
18+
19+
== Run the example
20+
21+
1. link:src/main/resources/application.yaml[configure your neo4j db] or use a public one
22+
2. run the link:src/main/kotlin/org/neo4j/graphql/examples/dgsspringboot/DgsSpringBootApplication.kt[spring boot application]
23+
3. open http://localhost:8080/graphiql to run some graphql queries e.g. try:
24+
25+
```graphql
26+
query{
27+
other
28+
movie (first: 3){
29+
title
30+
bar
31+
javaData {
32+
name
33+
}
34+
}
35+
}
36+
```
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.neo4j.graphql.examples.dgsspringboot
2+
3+
import org.springframework.boot.autoconfigure.SpringBootApplication
4+
import org.springframework.boot.runApplication
5+
6+
@SpringBootApplication
7+
open class DgsSpringBootApplication
8+
9+
fun main(args: Array<String>) {
10+
runApplication<DgsSpringBootApplication>(*args)
11+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.neo4j.graphql.examples.dgsspringboot.config
2+
3+
import com.netflix.graphql.dgs.DgsCodeRegistry
4+
import com.netflix.graphql.dgs.DgsComponent
5+
import com.netflix.graphql.dgs.DgsRuntimeWiring
6+
import com.netflix.graphql.dgs.DgsTypeDefinitionRegistry
7+
import graphql.schema.GraphQLCodeRegistry
8+
import graphql.schema.idl.RuntimeWiring
9+
import graphql.schema.idl.SchemaParser
10+
import graphql.schema.idl.TypeDefinitionRegistry
11+
import org.neo4j.graphql.DataFetchingInterceptor
12+
import org.neo4j.graphql.SchemaBuilder
13+
import org.springframework.beans.factory.annotation.Autowired
14+
import org.springframework.beans.factory.annotation.Value
15+
import org.springframework.core.io.Resource
16+
import javax.annotation.PostConstruct
17+
18+
19+
/**
20+
* Configuration of the GraphQL schemas
21+
*/
22+
@DgsComponent
23+
open class GraphQLConfiguration {
24+
25+
@Value("classpath:neo4j.graphql")
26+
lateinit var graphQl: Resource
27+
28+
@Autowired(required = false)
29+
lateinit var dataFetchingInterceptor: DataFetchingInterceptor
30+
31+
lateinit var schemaBuilder: SchemaBuilder
32+
33+
@PostConstruct
34+
fun postConstruct() {
35+
val schema = graphQl.inputStream.bufferedReader().use { it.readText() }
36+
val typeDefinitionRegistry = SchemaParser().parse(schema)
37+
schemaBuilder = SchemaBuilder(typeDefinitionRegistry)
38+
schemaBuilder.augmentTypes()
39+
}
40+
41+
@DgsTypeDefinitionRegistry
42+
fun registry(): TypeDefinitionRegistry {
43+
return schemaBuilder.typeDefinitionRegistry
44+
}
45+
46+
@DgsCodeRegistry
47+
fun codeRegistry(codeRegistryBuilder: GraphQLCodeRegistry.Builder, registry: TypeDefinitionRegistry): GraphQLCodeRegistry.Builder {
48+
schemaBuilder.registerDataFetcher(codeRegistryBuilder, dataFetchingInterceptor, registry)
49+
return codeRegistryBuilder
50+
}
51+
52+
@DgsRuntimeWiring
53+
fun runtimeWiring(builder: RuntimeWiring.Builder): RuntimeWiring.Builder {
54+
schemaBuilder.registerTypeNameResolver(builder)
55+
schemaBuilder.registerScalars(builder)
56+
return builder
57+
}
58+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.neo4j.graphql.examples.dgsspringboot.config
2+
3+
import graphql.schema.*
4+
import org.neo4j.driver.Driver
5+
import org.neo4j.driver.SessionConfig
6+
import org.neo4j.graphql.Cypher
7+
import org.neo4j.graphql.DataFetchingInterceptor
8+
import org.springframework.beans.factory.annotation.Value
9+
import org.springframework.context.annotation.Bean
10+
import org.springframework.context.annotation.Configuration
11+
import java.math.BigDecimal
12+
import java.math.BigInteger
13+
14+
/**
15+
* Configuration of the DataFetchingInterceptor
16+
*/
17+
@Configuration
18+
open class Neo4jConfiguration {
19+
20+
/**
21+
* This interceptor is bound to all the graphql fields generated by the neo4j-graphql-library.
22+
* Its purpose is the execution of the cypher query and the transformation of the query result.
23+
*/
24+
@Bean
25+
open fun dataFetchingInterceptor(driver: Driver, @Value("\${database}") database: String): DataFetchingInterceptor {
26+
return object : DataFetchingInterceptor {
27+
override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher<Cypher>): Any? {
28+
val (cypher, params, type, variable) = delegate.get(env)
29+
30+
return driver.session(SessionConfig.forDatabase(database)).writeTransaction { tx ->
31+
val boltParams = params.mapValues { toBoltValue(it.value) }
32+
val result = tx.run(cypher, boltParams)
33+
if (isListType(type)) {
34+
result.list()
35+
.map { record -> record.get(variable).asObject() }
36+
} else {
37+
result.list()
38+
.map { record -> record.get(variable).asObject() }
39+
.firstOrNull() ?: emptyMap<String, Any>()
40+
}
41+
}
42+
}
43+
}
44+
}
45+
46+
companion object {
47+
private fun toBoltValue(value: Any?) = when (value) {
48+
is BigInteger -> value.longValueExact()
49+
is BigDecimal -> value.toDouble()
50+
else -> value
51+
}
52+
53+
private fun isListType(type: GraphQLType?): Boolean = when (type) {
54+
is GraphQLList -> true
55+
is GraphQLNonNull -> isListType(type.wrappedType)
56+
else -> false
57+
}
58+
}
59+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.neo4j.graphql.examples.dgsspringboot.datafetcher
2+
3+
import com.netflix.graphql.dgs.DgsComponent
4+
import com.netflix.graphql.dgs.DgsData
5+
import graphql.schema.DataFetchingEnvironment
6+
import org.neo4j.graphql.examples.dgsspringboot.types.types.JavaData
7+
import java.util.*
8+
9+
10+
@DgsComponent
11+
class AdditionalDataFetcher {
12+
@DgsData(parentType = "Movie", field = "bar")
13+
fun bar(): String {
14+
return "foo"
15+
}
16+
17+
@DgsData(parentType = "Movie", field = "javaData")
18+
fun javaData(env: DataFetchingEnvironment): List<JavaData> {
19+
val title = env.getSource<Map<String, *>>()["title"]
20+
return Collections.singletonList(JavaData("test $title"))
21+
}
22+
23+
@DgsData(parentType = "Query", field = "other")
24+
fun other(): String {
25+
return "other"
26+
}
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
org:
2+
neo4j:
3+
driver:
4+
uri: bolt://demo.neo4jlabs.com:7687
5+
authentication:
6+
username: movies
7+
password: movies
8+
config :
9+
encrypted : true
10+
database: movies
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type Movie {
2+
title: String
3+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
extend type Movie {
2+
bar: String @ignore
3+
javaData: [JavaData!] @ignore
4+
}
5+
6+
type JavaData {
7+
name: String
8+
}
9+
10+
extend type Query {
11+
other: String
12+
}

0 commit comments

Comments
 (0)