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.

Tokens
32.3K
Snippets
161
Records
189
Agent score
75%

What's inside DGS Framework

  1. Overview of DGS and Spring for GraphQL Integration

    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.
  2. 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's Mono. Includes a built-in WebClient implementation.
    • ReactiveGraphQLClient: For streaming responses (e.g., Subscriptions) based on Project Reactor's Flux. Implemented by SSESubscriptionGraphQLClient and WebSocketGraphQLClient.
  3. Understand DGS built-in exception handling

    The DGS framework includes a default exception handler for data fetchers. By default, any RuntimeException is translated into a GraphQLError of type INTERNAL. Certain exceptions are automatically mapped to more specific GraphQL error types:

    • AccessDeniedException $\rightarrow$ PERMISSION_DENIED (triggered when a @Secured check fails)
    • DgsEntityNotFoundException $\rightarrow$ NOT_FOUND (thrown when a requested entity is not found)
  4. 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.

  5. Automatically 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, a TypeResolver is created automatically. In this scenario, no additional configuration or code is required to map Java instances to their corresponding GraphQL types.
  6. 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 a org.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));
    }
  7. 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.endDate
  8. Build type-safe Federated Queries with DGS Java Client

    The DGS Java Client allows for type-safe construction of _entities queries used in Apollo Federation. It uses EntitiesGraphQLQuery to manage representations (based on the @key fields and __typename defined in the schema) and EntitiesProjectionRoot to select the desired return fields. The representations are passed as a map of variables, which can be retrieved using getVariables() on the EntitiesGraphQLQuery object.
    // 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();
  9. Handle custom scalars in the type-safe Query API

    To use custom scalars (like LocalDateTime) in input types with the generated Query API, provide a Map<Class<?>, Coercing<?,?>> to the GraphQLQueryRequest constructor. This map links the Java class to the scalar's Coercing implementation, 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);
  10. Modify GraphQL response headers

    The legacy DGS mechanism using DgsRestController.DGS_RESPONSE_HEADERS_KEY to add custom response headers is no longer supported. Developers should now use the WebGraphQlInterceptor provided by Spring for GraphQL to modify response headers.
  11. Access the raw HTTP request object via DgsRequestData

    To access the underlying HTTP request or response (e.g., to set a cookie), retrieve DgsRequestData from the DgsContext within the DgsDataFetchingEnvironment. Because the implementation differs by stack, you must cast it to either DgsWebMvcRequestData (for WebMVC) or DgsReactiveRequestData (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;
    }