GithubHelp home page GithubHelp logo

spring-projects / spring-graphql Goto Github PK

View Code? Open in Web Editor NEW
1.5K 45.0 286.0 3.84 MB

Spring Integration for GraphQL

Home Page: https://spring.io/projects/spring-graphql

License: Apache License 2.0

Java 99.38% Shell 0.42% Dockerfile 0.02% HTML 0.11% CSS 0.07%
graphql spring-graphql

spring-graphql's Introduction

Spring for GraphQL Build status Revved up by Develocity

GraphQL support for Spring applications with GraphQL Java.

Code of Conduct

This project is governed by the Spring Code of Conduct. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to [email protected].

Documentation

This project has reference documentation (published and source), an API reference. There are samples in the 1.0.x branch that will be moved out into a separate repository.

Continuous Integration Builds

Information regarding CI builds can be found in the project's Concourse pipeline documentation.

Stay in Touch

Follow @SpringCentral.

Getting Support

Check out the Spring GraphQL tags on Stack Overflow. Commercial support is available too.

License

This project is released under version 2.0 of the Apache License.

spring-graphql's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-graphql's Issues

Add endpoint to print the schema

It is often very helpful to have an endpoint which returns the whole Schema as printed SDL.

This is in some way similar to the build-in Introspection, but "made for humans" and also the SDL is more rich than Introspection as it can contain comments and directives.

Support schema transformations

GraphQL java recently added recently support for directives exposed via Introspection. See graphql-java/graphql-java#2221

This is applied by modifying an existing schema:

schema = new IntrospectionWithDirectivesSupport().apply(schema)

Spring GraphQL should support this kind of schema modifications.

Flexible handling of DataFetcher exceptions

GraphQL Java can be configured with a DataFetcherExceptionHandler that handles DataFetcher exceptions by resolving to a list of corresponding GraphQLErrors to include in the response.

Spring GraphQL can build on this with its own default DataFetcherExceptionHandler that governs how exception handling is done overall. This default implementation can provide the following:

  • the ability to register of multiple independent exception resolver strategies.
  • built-in exception resolver implementations, e.g. based on Exception mappings, annotated exception handlers, etc.
  • built-in resolution of "standard" Spring-related exceptions, e.g. Spring Security related.
  • default handling for exceptions that remain unresolved that applications can customize as needed.

The exception resolver strategy could be something like this:

public interface DataFetcherExceptionResolver {

    /**
     * Return a (possibly empty) list of errors for the given exception, or null if not resolved
     * in order to give other resolvers a chance.
     */
    @Nullable
    List<GraphQLError> resolveException(DataFetcherExceptionHandlerParameters parameters);

}

The autoconfig can configure the Spring GraphQL DataFetcherExceptionHandler implementation and also look for one or more, ordered, DataFetcherExceptionResolver beans to be passed to the handler. Some of those resolvers would also be declared by default to take care of "standard" Spring-related exceptions, provide fallback handling, etc.

Refine GraphQL metrics

Now that #16 is done, we should work on refining the metrics and the associated tags.

Right now we're publishing a "graphql.query" timer metrics that's measuring the execution time of the query using a SimpleInstrumentation. We're adding a "query" tag with the actual query, and a "outcome" tag describing the outcome of the query (success or error).

There are quite a few things to refine here:

  1. the "query" tag is not right, since it's publishing the raw query. Micrometer tags should be bounded and useful for building graphs. Unfortunately, the number of possible queries sent by clients are unbounded, and they can be very long, making it hard to use as a basis for graphs. Is there a way to derive useful information from the query without those downsides?
  2. the "operation" and "errors" informations are not used as a tag. In general, what information could we use as tags?
  3. without entering the "tracing" domain, are there other things we could create metrics for?

Support for input type conversion in a DataFetcher

The schema content.

input CreatePostInput {
    title: String!
    content: String!
}

type Mutation {
    createPost(createPostInput: CreatePostInput!): UUID!
}

The the datafecthers:

    public DataFetcher<UUID> createPost() {
        return dfe -> {
            CreatePostInput input = dfe.getArgument("createPostInput");
            return postService.createPost(input);
        };
    }

The error report it can not cast a HashMap to CreatePostInput, in GraphQl java kickstart, there is no special step need to convert it. But Spring GraphQl does not provide a GraphQLMutationResolver interface or annotations.

I tried to add empty type in the RuntimeWiring config, it does not work.

Request-Scoped Data Loaders support

In the Netflix Dgs and GraphQL Java, there are some approaches to register the request-scoped DataLoaders to improve the performance, but I can not find such a thing in Spring GraphQL.

  1. Register DataLoader via DataLoaderRegistry(how to do this step?), my dataloaders example is here, Netflix Dgs has a simple annotation to do this.
  2. Get it from DataFetchingEnviroment.

Context Propagation to DataFetcher's with Spring MVC

For Spring Security support on Spring MVC, we'll need context propagation.

Initially the request is on a Servlet container thread with ThreadLocal context, then it passes through the Reactor web interception chain before getting to GraphQL where DataFetchers are invoked and those would be expected to be imperative but also potentially reactive.

We'll need to assist with context propagation for this to work seamlessly. It will probably involve some mechanism that enables Spring Security or others to help decide which ThreadLocal context to propagate.

Allow CORS configuration with spring.graphql.cors.* properties

One very common challenge for local development is that CORS doesn't allow requests from your web client to the Backend server on a different port.

One solution that worked for me is:

	@Bean
	public WebFluxConfigurer corsConfigurer() {

		return new WebFluxConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/graphql").allowedOrigins("http://localhost:3000");
			}
		};
	}

Question: Is this is the idiomatic Spring way?

We should document this clearly in spring-graphql.

Also: Is it worth making it easier somehow?

See graphql-java/graphql-java-spring#11 for the issue in graphql-java-spring.

Reactive DataFetcherExceptionResolver

DataFetcherExceptionResolver implementations need to be able to do non-blocking I/O and have access to the Reactor context, mirroring how a DataFetcher is now supported to return Flux or Mono.

For a concrete case, Spring Security needs to access security context when resolving an exception and on WebFlux this is most naturally obtained through the Reactor Context. Once obtained, the security context itself is deferred, in the form of Mono<SecurityContext> and hence the need for a non-blocking contract.

One option would be to change the contract to return Mono<List<GraphQLError>> rather than List<GraphQLError>. Another would be like a DataFetcher to support flexible return types, in this case checking for GraphQLError or a list, a Mono, or a Flux of them.

Ideally this would rely on a corresponding change in GraphQL Java to support an asynchronous DataFetcherExceptionHandler or otherwise we would need to switch threads ourselves to absorb the potential for blocking.

Secure Configuration for GraphiQL and Introspection

According to the OWASP cheatsheet for GraphQL, GraphiQL and introspection should not be on and accessible without authentication by default. For now simply having GraphiQL disabled by default, but beyond that we need to consider the options more broadly. How it works out of the box, how it is configured and controlled, how it relates to development mode, security settings, and so on.

GraphqlTester should not have web dependencies

There is nothing in GraphQlTester that is web specific, apart from the factory methods accepting WebTestClient and WebGraphqlHandler, but that'll change as we add web specific inputs for #64.

We can create a WebGraphQlTester that extends GraphQlTester, allows additional, web-specific inputs, and exposes web-specific factory methods, while GraphQlTester remains neutral to the underlying transport it is used with. In the future we may have other such extensions, for example for testing GraphQL requests over RSocket.

Support for multiple graphql schema files

The TypeDefinitionRegistry provides a .merge(TypedefinitionRegistry) to be able to merge multiple schema resources. This comes in mostly handy when the developer tries to create multiple graphql schema files to improve readability and simplicity. All these will be merged into one GraphQL schema/TypeDefinitionRegistry.

Remove Spring Boot starters

Our current Spring Boot starters aren't actually necessary: the basic profile of our apps should be Spring MVC or Spring WebFlux apps and the spring-graphql-web dependency should be a signal strong enough that we should add web endpoints to the existing applications and wire in a graphQL schema.

Add sample application

We should have a sample application in the repository to quickly test-drive features for a start.
This app should be deleted/extracted as soon as we're reaching some stability with the codebase.

Add spring-webflux to the dependecies of spring-graphql-test

When creating a WebMVC project and writing some tests with GraphQlTester, and I found the GraphQlTester is dependent on WebClient or WebTestClient which is part of spring-webflux.

So I think it is better to add spring-webflux as dependency of spring-graphql-test by default.

Add Interface for GraphQL Request Execution

Currently HTTP handlers for Spring MVC and WebFlux expect a GraphQL instance and a List<WebInterceptor>. This allows for interception of requests but not for replacing or mocking out the invocation of GraphQL.

WebSocket Endpoint To Be Explicitly Enabled

Subscriptions are less common and arguably a WebSocket endpoint should not be exposed unless explicitly configured. We could make it conditional on the presence of a property for the WebSocket path, similar to how the spring.rsocket.server.mapping-path is used to enable RSocket over WebSocket.

Add Support for Testing

It is not too difficult to send requests through the WebTestClient and check the data in the response body but it is verbose and not very convenient to do so.

Consider graphiQL integration

As of #12, we're shipping a very simple graphiQL integration with spring-graphql. The current version ships a single HTML page that depends on static resources on 3rd party CDNs, with well-known front-end libraries.

There are several issues with it that we should discuss:

  • this is the simplest version of a graphiQL integration, we could use a JS package manager to build a more efficient, self-contained version
  • the versions of the front-end libraries are hard-coded in the HTML page
  • there is currently no way to customize or override this page
  • the need/opportunities for customizations on that page
  • whether this should ship with spring-graphql or some separate module

Add a GraphQLClient

Like RestTemplate, WebClient, WebSocketClient, RSocketRequestor, add a Client to simplify the Client/Server communications using GraphQL-based APIs.

Simplify BatchLoader Registration

It is possible to use a BatchLoader as shown in #60 (comment) but it looks like boilerplate. We could detect beans of type BatchLoader and register them as data loaders. We should also explore options to wrap in order to support Flux/Mono return types along with context context propagation, just like we do already for data fetchers.

Add support for full "Serving over HTTP" spec

Currently only POST requests are routed: https://github.com/spring-projects-experimental/spring-graphql/blob/d7f3aa6fa9e67905d81aa9a7a51006f4eb283d4a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebMvcGraphQLAutoConfiguration.java#L51-L55

It would be nice to support the full spectrum as stated on https://graphql.org/learn/serving-over-http/

Serving over HTTP

HTTP is the most common choice for client-server protocol when using GraphQL because of its ubiquity. Here are some guidelines for setting up a GraphQL server to operate over HTTP.

Web Request Pipeline

Most modern web frameworks use a pipeline model where requests are passed through a stack of middleware (AKA filters/plugins). As the request flows through the pipeline, it can be inspected, transformed, modified, or terminated with a response. GraphQL should be placed after all authentication middleware, so that you have access to the same session and user information you would in your HTTP endpoint handlers.

URIs, Routes

HTTP is commonly associated with REST, which uses "resources" as its core concept. In contrast, GraphQL's conceptual model is an entity graph. As a result, entities in GraphQL are not identified by URLs. Instead, a GraphQL server operates on a single URL/endpoint, usually /graphql, and all GraphQL requests for a given service should be directed at this endpoint.

HTTP Methods, Headers, and Body

Your GraphQL HTTP server should handle the HTTP GET and POST methods.

GET request

When receiving an HTTP GET request, the GraphQL query should be specified in the "query" query string. For example, if we wanted to execute the following GraphQL query:

{
  me {
    name
  }
}

This request could be sent via an HTTP GET like so:

http://myapi/graphql?query={me{name}}

Query variables can be sent as a JSON-encoded string in an additional query parameter called variables. If the query contains several named operations, an operationName query parameter can be used to control which one should be executed.

POST request

A standard GraphQL POST request should use the application/json content type, and include a JSON-encoded body of the following form:

{
  "query": "...",
  "operationName": "...",
  "variables": { "myVariable": "someValue", ... }
}

operationName and variables are optional fields. operationName is only required if multiple operations are present in the query.

In addition to the above, we recommend supporting two additional cases:

If the "query" query string parameter is present (as in the GET example above), it should be parsed and handled in the same way as the HTTP GET case.
If the "application/graphql" Content-Type header is present, treat the HTTP POST body contents as the GraphQL query string.

This is similar as also added to https://github.com/graphql-java/graphql-java-spring via graphql-java/graphql-java-spring#4.

That support all the Serving over HTTP cases already: https://github.com/graphql-java/graphql-java-spring/blob/c6606f624d98b0835e719e0d48ab7223b141cd19/graphql-java-spring-webmvc/src/main/java/graphql/spring/web/servlet/components/GraphQLController.java#L45-L135 (just like Micronaut also does).

Encapsulate GraphQL initialization

Currently GraphQLSchema and GraphQL are initialized in Boot starter configuration. GraphQL Java provides builders to do all this but there is still glue code, e.g. schema transformations, the Reactor DataFetcher support, reloadable schema in development, and much more to come.

This initialization needs to be extracted out of configuration, encapsulated, and made flexible for extension.

Use chained handling for web requests

Currently WebInterceptor has preHandle and postHandle methods with Mono return types and each WebInterceptor is invoked nested within a flatMap. That means an interceptor cannot add to the Reactor context of the top-level flow which is where Reactor context gets transferred to the graphql.ExecutionInput.

If we switched to a chained handling model with delegation, like with WebFilter in WebFlux, that issue should go away, we won't need to use flatMap to connect the interceptors, and it would be more convenient to write an interceptor since "before" handling won't have to use any operators, e.g. if it doesn't do anything asynchronous.

One additional benefit of turning this into a chain is that we can have an independent GraphQLService at the end of the chain that invokes graphql.GraphQL and possibly has other invocation related behavior like the transfer of Reactor Context. Such a shared service could then be invoked through the Web layer, or through anything else.

Subscription support in Spring WebMvc

I tried to taste the Subscription in my Spring Graphql sample(written in Spring WebMvc).

When I wrote a test for the Subscriptions, failed with a 404 error.

debug info:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /graphql
       Parameters = {}
          Headers = [WebTestClient-Request-Id:"3", Content-Type:"application/json;charset=UTF-8", Accept:"text/event-stream", Content-Length:"337"]
             Body = {"query":"subscription onCommentAdded { commentAdded { id content } }","operationName":null,"variables":{},"uri":{"scheme":null,"fragment":null,"userInfo":null,"host":null,"port":-1,"path":"","queryParams":{},"query":null,"schemeSpecificPart":null,"pathSegments":[]},"headers":{},"id":"org.springframework.graphql.web.WebInput@2398a895"}
    Session Attrs = {}

Handler:
             Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 404
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Apply Spring JavaFormat to project

If we want to apply consistent code formatting rules across Spring projects and easily move the Spring Boot starter to the Spring Boot codebase, we should start enforcing similar formatting rules.

Allow HTTP and WebSocket To Use The Same Path

Currently we use a separate path property for WebSocket with a default of "/graphql/websocket" to avoid a collision with GET "/graphql" for GraphiQL. However this shouldn't be necessary. Given the WebSocket HandlerMapping is ordered ahead, we can make it skip requests that aren't a WebSocket upgrade.

Add Builder for GraphQlTester and WebGraphQlTester

Given the work for #65 to create a WebGraphQlTester extension of GraphQlTester, it would also be useful to work out their respective builder APIs with common configuration options, like JSON config, in the base and any Web specific options, like default HTTP headers, in the extension.

Support for WebInterceptor configuration

WebFluxGraphQLHandler and WebMvcGraphQLHandler now have a List<WebInterceptor> constructor arg which is initialized to an empty list by the auto config. We'll need some idiomatic way to configure those but I'm not sure if that should be under graphql or boot.graphql or both?

Introduce DataFetcher configuration helpers

ArtifactRepositoryDataWiring is very good example about wiring graphql query to spring data query interface.

I wonder how we like the idea of auto wiring the spring data repo. it is like below:

Assume I can get the repo->graphql binding info from properties or annotation.
binding.get("findAll") will give me graphql query and parameters if any . We just wiring it here. For user defined repo method. We need reflection to wire properly

It should works with most spring data project. Let me know whether this interest you please. Thanks!

  public class AutoDataWiring implements RuntimeWiringCustomizer {

    private final CrudRepository repository;
    private final Map<String, List<String>> binding;

    /**
     * @param repository
     * @param binding,   spring data repo method name -> list of  [query name,parameter1, parameter2...], could use a customer Type instead of List to hold more information.
     */
    public AutoDataWiring(CrudRepository repository, Map<String, List<String>> binding) {
        this.repository = repository;
        this.binding = binding;
    }

    @Override
    public void customize(RuntimeWiring.Builder builder) {
        try {
            // user defined query method
            Method findBySomethingMethod = repository.getClass().getMethod("findBySomething", String.class, String.class);

            builder.type("QueryType", typeWiring -> typeWiring
// todo: do it properly using reflection. 
                    .dataFetcher(binding.get("findAll").get(0), env -> this.repository.findAll())
                    .dataFetcher(binding.get("findById").get(0), env ->
                            this.repository.findById(env.getArgument(binding.get("findById").get(1)))
                    ).dataFetcher(binding.get("findBySomething").get(0), env ->
                            findBySomethingMethod.invoke(repository, env.getArgument(binding.get("findBySomething").get(1)), env.getArgument(binding.get("findBySomething").get(2)))
                    ));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.