Polish GraphQL changes

See gh-29140
Closes gh-29194
pull/29200/head
izeye 3 years ago committed by Brian Clozel
parent 0d616b8924
commit 728206dba0

@ -35,6 +35,12 @@ import io.micrometer.core.instrument.Timer;
import org.springframework.boot.actuate.metrics.AutoTimer; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/**
* Micrometer-based {@link SimpleInstrumentation}.
*
* @author Brian Clozel
* @since 2.7.0
*/
public class GraphQlMetricsInstrumentation extends SimpleInstrumentation { public class GraphQlMetricsInstrumentation extends SimpleInstrumentation {
private final MeterRegistry registry; private final MeterRegistry registry;
@ -127,7 +133,7 @@ public class GraphQlMetricsInstrumentation extends SimpleInstrumentation {
private Timer.Sample sample; private Timer.Sample sample;
private AtomicLong dataFetchingCount = new AtomicLong(0L); private final AtomicLong dataFetchingCount = new AtomicLong();
RequestMetricsInstrumentationState(AutoTimer autoTimer, MeterRegistry registry) { RequestMetricsInstrumentationState(AutoTimer autoTimer, MeterRegistry registry) {
this.timer = autoTimer.builder("graphql.request"); this.timer = autoTimer.builder("graphql.request");

@ -34,7 +34,7 @@ import org.springframework.util.CollectionUtils;
* Factory methods for Tags associated with a GraphQL request. * Factory methods for Tags associated with a GraphQL request.
* *
* @author Brian Clozel * @author Brian Clozel
* @since 1.0.0 * @since 2.7.0
*/ */
public final class GraphQlTags { public final class GraphQlTags {
@ -72,7 +72,7 @@ public final class GraphQlTags {
builder.append('$'); builder.append('$');
for (Object segment : pathSegments) { for (Object segment : pathSegments) {
try { try {
int index = Integer.parseUnsignedInt(segment.toString()); Integer.parseUnsignedInt(segment.toString());
builder.append("[*]"); builder.append("[*]");
} }
catch (NumberFormatException exc) { catch (NumberFormatException exc) {
@ -90,13 +90,13 @@ public final class GraphQlTags {
public static Tag dataFetchingPath(InstrumentationFieldFetchParameters parameters) { public static Tag dataFetchingPath(InstrumentationFieldFetchParameters parameters) {
ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo(); ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo();
StringBuilder dataFetchingType = new StringBuilder(); StringBuilder dataFetchingPath = new StringBuilder();
if (executionStepInfo.hasParent() && executionStepInfo.getParent().getType() instanceof GraphQLObjectType) { if (executionStepInfo.hasParent() && executionStepInfo.getParent().getType() instanceof GraphQLObjectType) {
dataFetchingType.append(((GraphQLObjectType) executionStepInfo.getParent().getType()).getName()); dataFetchingPath.append(((GraphQLObjectType) executionStepInfo.getParent().getType()).getName());
dataFetchingType.append('.'); dataFetchingPath.append('.');
} }
dataFetchingType.append(executionStepInfo.getPath().getSegmentName()); dataFetchingPath.append(executionStepInfo.getPath().getSegmentName());
return Tag.of("path", dataFetchingType.toString()); return Tag.of("path", dataFetchingPath.toString());
} }
} }

@ -85,7 +85,7 @@ class GraphQlMetricsInstrumentationTests {
Timer timer = this.registry.find("graphql.request").timer(); Timer timer = this.registry.find("graphql.request").timer();
assertThat(timer).isNotNull(); assertThat(timer).isNotNull();
assertThat(timer.takeSnapshot().count()).isEqualTo(1); assertThat(timer.count()).isEqualTo(1);
} }
@Test @Test
@ -120,7 +120,7 @@ class GraphQlMetricsInstrumentationTests {
Timer timer = this.registry.find("graphql.datafetcher").timer(); Timer timer = this.registry.find("graphql.datafetcher").timer();
assertThat(timer).isNotNull(); assertThat(timer).isNotNull();
assertThat(timer.takeSnapshot().count()).isEqualTo(1); assertThat(timer.count()).isEqualTo(1);
} }
@Test @Test
@ -135,7 +135,7 @@ class GraphQlMetricsInstrumentationTests {
Timer timer = this.registry.find("graphql.datafetcher").timer(); Timer timer = this.registry.find("graphql.datafetcher").timer();
assertThat(timer).isNotNull(); assertThat(timer).isNotNull();
assertThat(timer.takeSnapshot().count()).isEqualTo(1); assertThat(timer.count()).isEqualTo(1);
} }
@Test @Test
@ -150,7 +150,7 @@ class GraphQlMetricsInstrumentationTests {
Timer timer = this.registry.find("graphql.datafetcher").timer(); Timer timer = this.registry.find("graphql.datafetcher").timer();
assertThat(timer).isNotNull(); assertThat(timer).isNotNull();
assertThat(timer.takeSnapshot().count()).isEqualTo(1); assertThat(timer.count()).isEqualTo(1);
} }
@Test @Test

@ -168,7 +168,7 @@ class GraphQlWebMvcSecurityAutoConfigurationTests {
} }
@Bean @Bean
static InMemoryUserDetailsManager userDetailsService() { InMemoryUserDetailsManager userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder(); User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build(); UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build(); UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();

@ -905,7 +905,7 @@ A single GraphQL query can involve many `DataFetcher` calls, so there is a dedic
The `graphql.request.datafetch.count` https://micrometer.io/docs/concepts#_distribution_summaries[distribution summary] counts the number of non-trivial `DataFetcher` calls made per request. The `graphql.request.datafetch.count` https://micrometer.io/docs/concepts#_distribution_summaries[distribution summary] counts the number of non-trivial `DataFetcher` calls made per request.
This metric is useful for detecting "N+1" data fetching issues and consider batch loading; it provides the `"TOTAL"` number of data fetcher calls made over the `"COUNT"` of recorded requests, as well as the `"MAX"` calls made for a single request over the considered period. This metric is useful for detecting "N+1" data fetching issues and considering batch loading; it provides the `"TOTAL"` number of data fetcher calls made over the `"COUNT"` of recorded requests, as well as the `"MAX"` calls made for a single request over the considered period.
More options are available for <<application-properties#application-properties.actuator.management.metrics.distribution.maximum-expected-value, configuring distributions with application properties>>. More options are available for <<application-properties#application-properties.actuator.management.metrics.distribution.maximum-expected-value, configuring distributions with application properties>>.
A single response can contain many GraphQL errors, counted by the `graphql.error` counter: A single response can contain many GraphQL errors, counted by the `graphql.error` counter:

@ -49,7 +49,7 @@ You can declare `RuntimeWiringConfigurer` beans in your Spring config to get acc
Spring Boot detects such beans and adds them to the {spring-graphql-docs}#execution-graphqlsource[GraphQlSource builder]. Spring Boot detects such beans and adds them to the {spring-graphql-docs}#execution-graphqlsource[GraphQlSource builder].
Typically, however, applications will not implement `DataFetcher` directly and will instead create {spring-graphql-docs}#controllers[annotated controllers]. Typically, however, applications will not implement `DataFetcher` directly and will instead create {spring-graphql-docs}#controllers[annotated controllers].
Spring Boot will automatically register `@Controller` classes with annotated handler methods and registers those as `DataFetcher`s. Spring Boot will automatically detect `@Controller` classes with annotated handler methods and register those as `DataFetcher`s.
Here's a sample implementation for our greeting query with a `@Controller` class: Here's a sample implementation for our greeting query with a `@Controller` class:
[source,java,indent=0,subs="verbatim"] [source,java,indent=0,subs="verbatim"]

@ -25,11 +25,11 @@ import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@Controller @Controller
public class ProjectsController { public class ProjectController {
private final List<Project> projects; private final List<Project> projects;
public ProjectsController() { public ProjectController() {
this.projects = Arrays.asList(new Project("spring-boot", "Spring Boot"), this.projects = Arrays.asList(new Project("spring-boot", "Spring Boot"),
new Project("spring-graphql", "Spring GraphQL"), new Project("spring-framework", "Spring Framework")); new Project("spring-graphql", "Spring GraphQL"), new Project("spring-framework", "Spring Framework"));
} }

@ -28,13 +28,13 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;
@Configuration @Configuration(proxyBeanMethods = false)
@EnableWebSecurity @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig { public class SecurityConfig {
@Bean @Bean
DefaultSecurityFilterChain springWebFilterChain(HttpSecurity http) throws Exception { public DefaultSecurityFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
return http.csrf((csrf) -> csrf.disable()) return http.csrf((csrf) -> csrf.disable())
// Demonstrate that method security works // Demonstrate that method security works
// Best practice to use both for defense in depth // Best practice to use both for defense in depth
@ -43,7 +43,7 @@ public class SecurityConfig {
@Bean @Bean
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public static InMemoryUserDetailsManager userDetailsService() { public InMemoryUserDetailsManager userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder(); User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build(); UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build(); UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();

@ -22,7 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest; import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.graphql.test.tester.GraphQlTester; import org.springframework.graphql.test.tester.GraphQlTester;
@GraphQlTest(ProjectsController.class) @GraphQlTest(ProjectController.class)
class ProjectControllerTests { class ProjectControllerTests {
@Autowired @Autowired

Loading…
Cancel
Save