Netflix DGS Framework
website·Indexed 30 days ago
https://netflix.github.io/dgs/A Java and Kotlin framework for building GraphQL APIs, featuring support for Apollo Federation, data loaders to solve the N+1 problem, and integration with Spring for GraphQL. It provides tools for code generation from schemas, custom scalars, directives, and a type-safe Java GraphQL Client. Version 10.0.0 released December 17, 2024.
What's inside DGS Framework
- The DGS Framework now integrates with Spring for GraphQL internally. This allows DGS users to leverage new features from Spring for GraphQL without reimplementing them. While users can mix DGS and Spring-GraphQL programming models, it is recommended to stick with the DGS programming model (e.g., using DGS annotations for data fetchers and data loaders) to maintain consistency and full capability access.
Overview of DGS Java GraphQL Client interfaces
The DGS framework provides a GraphQL client for retrieving data from endpoints or performing integration testing. It consists of a HTTP client wrapper for parsing responses and an optional Query API codegen for type-safe builders. Depending on the concurrency model, three interfaces are available:
GraphQLClient: For blocking implementations (recommended only with blocking HTTP clients).MonoGraphQLClient: For non-blocking implementations based on Project Reactor'sMono. Includes a built-in WebClient implementation.ReactiveGraphQLClient: For streaming responses (e.g., Subscriptions) based on Project Reactor'sFlux. Implemented bySSESubscriptionGraphQLClientandWebSocketGraphQLClient.
Understand DGS built-in exception handling
The DGS framework includes a default exception handler for data fetchers. By default, any
RuntimeExceptionis translated into aGraphQLErrorof typeINTERNAL. Certain exceptions are automatically mapped to more specific GraphQL error types:AccessDeniedException$\rightarrow$PERMISSION_DENIED(triggered when a@Securedcheck fails)DgsEntityNotFoundException$\rightarrow$NOT_FOUND(thrown when a requested entity is not found)
Align DGS dependency versions using the Platform BOM
The DGS Framework provides two Bill of Materials (BOM) to prevent version conflicts and ensure compatible dependency alignment.
graphql-dgs-platform: Aligns versions only for DGS-specific modules.graphql-dgs-platform-dependencies: Aligns versions for DGS modules AND their core dependencies, such as Spring, Jackson, and Kotlin.
Using a BOM allows you to define the DGS Framework version in one place, automatically applying that version to all included DGS dependencies.
Implement a Federated GraphQL architecture with DGS
To implement a Federated GraphQL architecture using the DGS framework and Apollo Gateway, refer to the official Federation examples repository.Federation Example: https://github.com/Netflix/dgs-federation-exampleAutomatically resolve GraphQL Interface and Union types
In the DGS framework, if the Java class name exactly matches the GraphQL type name defined in the schema, aTypeResolveris created automatically. In this scenario, no additional configuration or code is required to map Java instances to their corresponding GraphQL types.Implement a GraphQL Subscription data fetcher in DGS
In the DGS framework, a subscription is implemented as a data fetcher annotated with@DgsSubscription(which is a shorthand for@DgsData(parentType = "Subscription")). Unlike standard queries or mutations, a subscription data fetcher must return aorg.reactivestreams.Publisher. The Spring Framework's Reactor library (e.g.,Flux) is typically used to implement this interface.import reactor.core.publisher.Flux; import org.reactivestreams.Publisher; import java.time.Duration; @DgsSubscription public Publisher<Stock> stocks() { // Create a never-ending Flux that emits an item every second return Flux.interval(Duration.ofSeconds(1)).map(t -> new Stock("NFLX", 500 + t)); }Use Kotlin-native data classes for GraphQL responses
The experimental Kotlin API generates data classes where fields are wrapped in suppliers (
() -> T). This allows the system to distinguish between a field that is explicitly null (defined in the schema as nullable) and a field that was not requested in the GraphQL query. Accessing a field that was not requested will throw an exception, helping catch errors earlier than standard nullable types.Fields can be populated using the constructor with closures or via generated builder methods. The supplier closure is hidden; fields are accessed as standard properties.
// Using constructor with closures val series = Series( title = { "Stranger Things" }, actors = { listOf("Millie Bobby Brown", "Finn Wolfhard") }, ) // Using builder methods val seriesBuilder = Series.Builder() .withTitle("Stranger Things") .withReleaseDate(2016) .withEndDate(2024) .build() // Explicitly setting a requested field to null val seriesNull = Series( title = { "Black Mirror" }, endDate = { null }, ) // Accessing fields (throws exception if field was not requested/populated) val title: String = series.title val endDate: Int? = seriesNull.endDateBuild type-safe Federated Queries with DGS Java Client
The DGS Java Client allows for type-safe construction of_entitiesqueries used in Apollo Federation. It usesEntitiesGraphQLQueryto manage representations (based on the@keyfields and__typenamedefined in the schema) andEntitiesProjectionRootto select the desired return fields. The representations are passed as a map of variables, which can be retrieved usinggetVariables()on theEntitiesGraphQLQueryobject.// 1. Build the entities query and add representations as variables EntitiesGraphQLQuery entitiesQuery = new EntitiesGraphQLQuery.Builder() .addRepresentationAsVariable( MovieRepresentation.newBuilder().movieId(1122).build() ) .build(); // 2. Create the request with the query and the projection root GraphQLQueryRequest request = new GraphQLQueryRequest( entitiesQuery, new EntitiesProjectionRoot().onMovie().movieId().script().title() ); // 3. Serialize to final query string and extract variables String query = request.serialize(); Map<String, Object> representations = entitiesQuery.getVariables();Handle custom scalars in the type-safe Query API
To use custom scalars (likeLocalDateTime) in input types with the generated Query API, provide aMap<Class<?>, Coercing<?,?>>to theGraphQLQueryRequestconstructor. This map links the Java class to the scalar'sCoercingimplementation, ensuring the client uses the correct serialization format expected by the server.Map<Class<?>, Coercing<?, ?>> scalars = Map.of(LocalDateTime.class, DateTimeScalar.INSTANCE.getCoercing()); new GraphQLQueryRequest( ReviewsGraphQLQuery.newRequest().dateRange(new DateRange(LocalDate.of(2020, 1, 1), LocalDate.now())).build(), new ReviewsProjectionRoot().submittedDate().starScore(), scalars);Modify GraphQL response headers
The legacy DGS mechanism usingDgsRestController.DGS_RESPONSE_HEADERS_KEYto add custom response headers is no longer supported. Developers should now use theWebGraphQlInterceptorprovided by Spring for GraphQL to modify response headers.Access the raw HTTP request object via DgsRequestData
To access the underlying HTTP request or response (e.g., to set a cookie), retrieveDgsRequestDatafrom theDgsContextwithin theDgsDataFetchingEnvironment. Because the implementation differs by stack, you must cast it to eitherDgsWebMvcRequestData(for WebMVC) orDgsReactiveRequestData(for WebFlux).// WebMVC Example @DgsQuery public String updateCookie(@InputArgument String value, DgsDataFetchingEnvironment dfe) { DgsWebMvcRequestData requestData = (DgsWebMvcRequestData) dfe.getDgsContext().getRequestData(); ServletWebRequest webRequest = (ServletWebRequest) requestData.getWebRequest(); javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie("mydgscookie", value); webRequest.getResponse().addCookie(cookie); return value; } // WebFlux Example @DgsMutation public String updateCookieWebFlux(@InputArgument String value, DgsDataFetchingEnvironment dfe) { DgsReactiveRequestData requestData = (DgsReactiveRequestData) dfe.getDgsContext().getRequestData(); ServerRequest serverRequest = requestData.getServerRequest(); serverRequest.exchange().getResponse() .addCookie(ResponseCookie.from("mydgscookie", "webfluxupdated").build()); return value; }