Skip to content

Commit 6787f20

Browse files
authored
Add support for GraphQL schema query field names and result types customizations (#419)
* Add support for GraphQL schema query field names and result type customizations * Add initial spring-graphql-web example with mutations * apply prettier formatting * introduce EntityObjectTypeMetadata registry * Add BooksSchemaCustomizerBuildTest * update BooksSchemaCustomizerBuildTest * add BooksSchemaCustomizerBuildTest tests * Add example graphql query ApplicationTest * Add MutationControllerTest * Add SubscriptionControllerTest * Extract GraphQLSchemaConfiguration from Application * Polish GraphQLObjectTypeMetadata * add notifyBooksCreated test * Refine example graphql schema merge to filter null placeholders * add queryResultTypeNameCustomizer example * refine CodeRegistryVisitor to remove deprecated use of getDataFetcher * polish
1 parent a19f2cb commit 6787f20

File tree

21 files changed

+839
-98
lines changed

21 files changed

+839
-98
lines changed

autoconfigure/src/main/java/com/introproventures/graphql/jpa/query/autoconfigure/GraphQLSchemaFactoryBean.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ protected GraphQLSchema createInstance() {
121121
.map(GraphQLSchema::getQueryType)
122122
.map(GraphQLObjectType::getFieldDefinitions)
123123
.flatMap(Collection::stream)
124+
.filter(fd -> !"null".equals(fd.getName()))
124125
.collect(Collectors.toList());
125126

126127
List<GraphQLFieldDefinition> subscriptions = Stream
@@ -270,7 +271,7 @@ public TraversalControl visitGraphQLFieldDefinition(
270271
GraphQLFieldDefinition node,
271272
TraverserContext<GraphQLSchemaElement> context
272273
) {
273-
GraphQLFieldsContainer parentContainerType = (GraphQLFieldsContainer) context.getParentContext().thisNode();
274+
GraphQLObjectType parentContainerType = (GraphQLObjectType) context.getParentContext().thisNode();
274275
FieldCoordinates coordinates = parentContainerType.equals(containerType)
275276
? coordinates(typeName, node.getName())
276277
: coordinates(parentContainerType, node);

examples/spring-graphql-web/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@
5151
<artifactId>spring-webflux</artifactId>
5252
<scope>test</scope>
5353
</dependency>
54+
<dependency>
55+
<groupId>io.projectreactor</groupId>
56+
<artifactId>reactor-test</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>io.projectreactor.netty</groupId>
61+
<artifactId>reactor-netty</artifactId>
62+
<scope>test</scope>
63+
</dependency>
5464
<dependency>
5565
<groupId>org.springframework.graphql</groupId>
5666
<artifactId>spring-graphql-test</artifactId>

examples/spring-graphql-web/src/main/java/com/introproventures/graphql/jpa/query/example/Application.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,10 @@
1515
*/
1616
package com.introproventures.graphql.jpa.query.example;
1717

18-
import com.introproventures.graphql.jpa.query.autoconfigure.EnableGraphQLJpaQuerySchema;
19-
import com.introproventures.graphql.jpa.query.schema.model.book.Book;
2018
import org.springframework.boot.SpringApplication;
2119
import org.springframework.boot.autoconfigure.SpringBootApplication;
2220

23-
/**
24-
* GraphQL JPA Query Example with Spring Boot Autoconfiguration
25-
*
26-
* You can configure GraphQL JPA Query properties in application.yml
27-
*
28-
* @author Igor Dianov
29-
*
30-
*/
3121
@SpringBootApplication
32-
@EnableGraphQLJpaQuerySchema(basePackageClasses = Book.class)
3322
public class Application {
3423

3524
public static void main(String[] args) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.introproventures.graphql.jpa.query.example.config;
2+
3+
import com.introproventures.graphql.jpa.query.autoconfigure.EnableGraphQLJpaQuerySchema;
4+
import com.introproventures.graphql.jpa.query.autoconfigure.GraphQLJPASchemaBuilderCustomizer;
5+
import com.introproventures.graphql.jpa.query.example.controllers.dto.CreateBookResult;
6+
import com.introproventures.graphql.jpa.query.schema.model.book.Book;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import reactor.core.publisher.Sinks;
10+
11+
@Configuration
12+
@EnableGraphQLJpaQuerySchema(basePackageClasses = Book.class)
13+
public class GraphQLSchemaConfiguration {
14+
15+
@Bean
16+
GraphQLJPASchemaBuilderCustomizer graphQLJPASchemaBuilderCustomizer() {
17+
return builder ->
18+
builder
19+
.graphQLIDType(true)
20+
.queryByIdFieldNameCustomizer("find%sById"::formatted)
21+
.queryAllFieldNameCustomizer("findAll%s"::formatted)
22+
.queryResultTypeNameCustomizer("%sQueryResult"::formatted);
23+
}
24+
25+
@Bean
26+
Sinks.Many<CreateBookResult> createBookResultSink() {
27+
return Sinks.many().replay().latest();
28+
}
29+
}
Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,74 @@
11
package com.introproventures.graphql.jpa.query.example.controllers;
22

3+
import com.introproventures.graphql.jpa.query.example.controllers.dto.CreateBookInput;
4+
import com.introproventures.graphql.jpa.query.example.controllers.dto.CreateBookResult;
5+
import com.introproventures.graphql.jpa.query.example.repository.BookRepository;
6+
import com.introproventures.graphql.jpa.query.schema.model.book.Author;
7+
import com.introproventures.graphql.jpa.query.schema.model.book.Book;
8+
import jakarta.persistence.EntityManager;
9+
import java.util.List;
10+
import java.util.function.Function;
311
import org.springframework.graphql.data.method.annotation.Argument;
412
import org.springframework.graphql.data.method.annotation.MutationMapping;
513
import org.springframework.stereotype.Controller;
14+
import reactor.core.publisher.Flux;
15+
import reactor.core.publisher.Mono;
16+
import reactor.core.publisher.Sinks;
617

718
@Controller
819
public class MutationController {
920

21+
private final EntityManager entityManager;
22+
23+
private final BookRepository bookRepository;
24+
25+
private final Sinks.Many<CreateBookResult> createBookResultSink;
26+
27+
public MutationController(
28+
EntityManager entityManager,
29+
BookRepository bookRepository,
30+
Sinks.Many<CreateBookResult> createBookResultSink
31+
) {
32+
this.entityManager = entityManager;
33+
this.bookRepository = bookRepository;
34+
this.createBookResultSink = createBookResultSink;
35+
}
36+
37+
@MutationMapping
38+
public Mono<CreateBookResult> createBook(@Argument CreateBookInput bookInput) {
39+
return Mono.just(bookInput).map(this::doCreateBook);
40+
}
41+
1042
@MutationMapping
11-
String echo(@Argument String name) {
12-
return name;
43+
public Flux<CreateBookResult> createBooks(@Argument List<CreateBookInput> bookInputs) {
44+
return Flux.fromStream(bookInputs.stream()).map(this::doCreateBook);
45+
}
46+
47+
CreateBookResult doCreateBook(CreateBookInput input) {
48+
return createBookEntity()
49+
.andThen(bookRepository::save)
50+
.andThen(createBookResult())
51+
.andThen(notifyCreateBookResult())
52+
.apply(input);
53+
}
54+
55+
Function<CreateBookInput, Book> createBookEntity() {
56+
return bookInput -> {
57+
Book book = new Book();
58+
book.setTitle(bookInput.title());
59+
book.setAuthor(entityManager.getReference(Author.class, bookInput.authorId()));
60+
return book;
61+
};
62+
}
63+
64+
Function<CreateBookResult, CreateBookResult> notifyCreateBookResult() {
65+
return result -> {
66+
createBookResultSink.emitNext(result, Sinks.EmitFailureHandler.FAIL_FAST);
67+
return result;
68+
};
69+
}
70+
71+
Function<Book, CreateBookResult> createBookResult() {
72+
return book -> CreateBookResult.builder().id(book.getId()).build();
1373
}
1474
}
Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.introproventures.graphql.jpa.query.example.controllers;
22

3-
import java.util.Date;
4-
import java.util.Random;
53
import org.springframework.graphql.data.method.annotation.Argument;
64
import org.springframework.graphql.data.method.annotation.QueryMapping;
75
import org.springframework.stereotype.Controller;
@@ -13,14 +11,4 @@ public class QueryController {
1311
String hello(@Argument String name) {
1412
return "Greetings, " + name + "!";
1513
}
16-
17-
@QueryMapping
18-
Long random() {
19-
return new Random().nextLong();
20-
}
21-
22-
@QueryMapping
23-
Date now() {
24-
return new Date();
25-
}
2614
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
package com.introproventures.graphql.jpa.query.example.controllers;
22

3+
import com.introproventures.graphql.jpa.query.example.controllers.dto.CreateBookResult;
34
import java.time.Duration;
45
import java.time.Instant;
6+
import java.util.List;
57
import java.util.stream.Stream;
8+
import org.reactivestreams.Publisher;
9+
import org.springframework.beans.factory.annotation.Autowired;
610
import org.springframework.graphql.data.method.annotation.Argument;
711
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
812
import org.springframework.stereotype.Controller;
913
import reactor.core.publisher.Flux;
14+
import reactor.core.publisher.Sinks;
1015

1116
@Controller
1217
public class SubscriptionController {
1318

19+
@Autowired
20+
private Sinks.Many<CreateBookResult> createBookResultSink;
21+
1422
@SubscriptionMapping
1523
Flux<String> greetings(@Argument String name) {
1624
return Flux
1725
.fromStream(Stream.generate(() -> "Hello, " + name + "@ " + Instant.now()))
1826
.delayElements(Duration.ofSeconds(1))
1927
.take(10);
2028
}
29+
30+
@SubscriptionMapping
31+
Publisher<List<CreateBookResult>> notifyBookCreated() {
32+
return createBookResultSink.asFlux().buffer(Duration.ofSeconds(1));
33+
}
2134
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.introproventures.graphql.jpa.query.example.controllers.dto;
2+
3+
public record CreateBookInput(String title, Long authorId) {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.introproventures.graphql.jpa.query.example.controllers.dto;
2+
3+
public record CreateBookResult(Long id) {
4+
public static Builder builder() {
5+
return new Builder();
6+
}
7+
8+
public static final class Builder {
9+
10+
private Long id;
11+
12+
private Builder() {}
13+
14+
public Builder id(Long id) {
15+
this.id = id;
16+
return this;
17+
}
18+
19+
public CreateBookResult build() {
20+
return new CreateBookResult(id);
21+
}
22+
}
23+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.introproventures.graphql.jpa.query.example.repository;
2+
3+
import com.introproventures.graphql.jpa.query.schema.model.book.Book;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface BookRepository extends JpaRepository<Book, Long> {}

0 commit comments

Comments
 (0)