Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Spring Framework GraphQL Library

License

NotificationsYou must be signed in to change notification settings

yandooo/spring-graphql-common

Repository files navigation

Table of Contents

Spring Framework GraphQL Library

Build StatusDownload

Intro

Initially project was inspired by multiple projects

  • multiple graphql-codegen projects (python-graphene, graphql-swift etc.)

and main idea was transformed into generic lightweight mechanism for schema definition in java.It will be helpful for those who starts coding schema from scratch or upgrading technology stack moving to GraphQL.

The library facilitates GraphQL schema development in SpringFramework environment heavily using annotations.The entire schema lives in Spring context which means developer can leverage all Spring features like AOP, IO etc.There is one important dependency ongraphql-java for schema imperative building and GraphQL query execution.The query execution strategy for the READ and MUTATE queries is based on Reactor Reactive Streams with high level of parallelism.

Note:process has been started on moving to complete Reactor RS Stack leveraging Netty GraphQL NIO server (spring boot starters should be updated as well).Reactor execution strategy will be strategic one available in the future releases.RxJava-based strategies are deprecated and won't be maintained anymore.

graphql-spring-boot-starter andgraphiql-spring-boot-starter are available ingraphql-spring-boot repository.

Requires

  • Java 1.8
  • Spring Framework v4.x (core & context)
  • java-graphql v2.0+
  • Reflections v0.9.10
  • Apache Commons Lang v3.4
  • Jackson v2.6.x
  • RxJava v1.1.1deprecated
  • RxJava Math v1.0.0deprecated
  • Slf4j

some of the dependencies can be removed in the future.

repositories {// stable build    jcenter()// development build    maven { url"http://dl.bintray.com/oembedler/maven" }}

Dependency:

dependencies {  compile'com.embedler.moon.graphql:spring-graphql-common:INSERT_LATEST_VERSION_HERE'}

How to use the latest build with Maven:

<repository>    <snapshots>        <enabled>false</enabled>    </snapshots>    <id>bintray-oembedler-maven</id>    <name>bintray</name>    <url>http://dl.bintray.com/oembedler/maven</url></repository>

Dependency:

<dependency>    <groupId>com.embedler.moon.graphql</groupId>    <artifactId>spring-graphql-common</artifactId>    <version>LATEST_VERSION_HERE</version></dependency>

Usage

The entire schema definition is annotations driven.There is a good support for generic types - so don't miss chance to use them if it's appropriate (seeRelay Support as an example).Class level annotations (like@GraphQLSchema,@GraphQLObject etc) are Spring@Component annotations.

@GraphQLSchemapublicclassTodoSchema {@GraphQLSchemaQueryprivateRootObjectTyperoot;publicstaticclassAddTodoIn {privateStringtext;    }// default value provider for an input mutation parameterpublicAddTodoIngetAddTodoInputDefaultValue() {AddTodoInaddTodoInput =newAddTodoIn();addTodoInput.setText("--- default text ---");returnaddTodoInput;    }@GraphQLMutationpublic@GraphQLOut("todoEdge")TodoObjectType.TodoEdgeObjectTypeaddTodoMutation(@GraphQLIn("addTodoInput",defaultProvider ="getAddTodoInputDefaultValue")AddTodoInaddTodoInput) {// mutation implementaion goes herereturntodoEdgeObjectType;    }

Use@GraphQLSchemaQuery to set root query for the schema.

Supported types

All Java wrapper types directly map to GraphQL types:

  • String -> GraphQLString
  • Character -> GraphQLString
  • Boolean -> GraphQLBoolean
  • Integer -> GraphQLInt
  • Long -> GraphQLLong
  • Float -> GraphQLFloat

same for corresponding Java primitives.Long,Float types are passed in the GraphQL queries as aStrings.It's due to underlying query parser implementation.

Extensions:

  • Double -> GraphQLDouble
  • Date -> GraphQLDateType (format defined in configuration)
  • LocalDateTime (JDK 8) -> GraphQLLocalDateTimeType (format defined in configuration)
  • Timestamp -> GraphQLTimestamp (milliseconds since 1970 1st Jan)

Double,Date andTimestamp values are passed in the GraphQL queries as aString values.

  • JavaEnum type is directly mapped toGraphQLEnumType.
  • JavaInterface is mapped toGraphQLInterfaceType if marked withGraphQLInterface annotation.
  • JavaInterface is mapped toGraphQLUnionType if marked withGraphQLUnion annotation.
  • JavaList andArray collections are automatically wrapped intoGraphQLList.
  • GraphQLNon-null element can be marked as such using@GraphQLNonNull annotation.
  • GraphQL ID element can be marked as such using@GraphQLID annotation.

Creating a new Object Type

@GraphQLObject annotation is used to mark class asGraphQLObjectType.Recursive object type references are handled automatically.

@GraphQLObject("Root")publicclassRootObjectType {@GraphQLNonNull@GraphQLDescription("Root query version number")privateStringversion;@GraphQLField@GraphQLDescription("Root viwer node as per Relay spec")publicUserObjectTypeviewer(/** no input expected **/) {// custom data fetcher for the field with name 'viewer'returnuserObjectType;    }}

If class field is accessible through getters \ setters - define field as a class member.All class fields are included into object definition unless@GraphQLIgnore annotation is used.If field needs custom data fetcher - define field as a class method and mark class with@GraphQLField annotation.@GraphQLDescription annotation can be used in most likely any context to set description for the GraphQL schema element.

Creating a new Interface Type

If interface must be considered as a part of the object hierarchy useGraphQLInterface annotation.Interfaces not marked with the annotation are ignored.

@GraphQLInterface("Node")publicinterfaceRelayNode {@GraphQLID("id")@GraphQLNonNull@GraphQLDescription("GraphQL Relay global object unique identifier")StringgetId(RelayNoderelayNode);}

Creating a new Enum Type

Java enums are automatically discovered and mapped toGraphQLEnumType.

// annotation is not required if enum names are acceptable as values@GraphQLEnum(valueProvider ="getValue")publicenumEpisode {@GraphQLDescription("Released in 1977.")NEWHOPE,@GraphQLDescription("Released in 1980.")EMPIRE,@GraphQLDescription("Released in 1983.")JEDI;// enum field value provider must be static method with 1 input argument - its own enumeration typepublicstaticObjectgetValue(Episodeself) {if (self ==NEWHOPE)return4;elseif (self ==EMPIRE)return5;elsereturn6;        }    }

@GraphQLEnum is not required for enum defition as Java enums are discovered automatically by library.On the other side using that annotation enum name and value can be changed.Note optionalvalueProvider annotation element should point to static method in enum which accepts one single argument -enum itself and returns value for the enum.The return value can be of any desired type.Also default value for enum can be provided through SpEL expression as follows:

// avaliable context objects are `obj` (enum instance itself) and `cls` - enum class@GraphQLEnum(defaultSpel ="#obj.getActualValue()")publicenumEpisodeV2 {@GraphQLDescription("Released in 1977.")NEWHOPE(4),@GraphQLDescription("Released in 1980.")EMPIRE(5),@GraphQLDescription("Released in 1983.")JEDI(6);intactualValue;EpisodeV2(intval) {actualValue =val;        }publicintgetActualValue() {returnactualValue;        }    }

Creating a new Union Type

GraphQL Union type is a java interface marked with@GraphQLUnion annotation.While annotation value (type name) can be emptypossibleType annotation field must contain list of possible types for union type.

@GraphQLUnion(value ="Pet",possibleTypes = {Dog.class,Cat.class})publicinterfacePetsUnionType {// empty marker// possible types must implement this interface    }//...@GraphQLField("pets")publicList<PetsUnionType>getPets() {List<PetsUnionType>pets =newArrayList<>();pets.addAll(cats);pets.addAll(dogs);returnpets;    }

Creating a Object-Input Type

Input method arguments are automatically converted intoGraphQLInputObjectType.All expected input arguments to either mutation or data fetcher have to be marked with@GraphQLIn annotation.Names for the method parameters can be automatically discovered usingorg.springframework.core.DefaultParameterNameDiscoverer.However it's recommended to define parameter name in@GraphQLIn annotation value field.To provide default value for an input parameter thedefaultProvider annotation field should point to the local class method (either static or instance).The return value of the method must be of the same type (derived type) as defined in a mutation method signature.

@GraphQLFieldpublicTodoObjectType.TodoConnectionObjectTypetodos(@GraphQLInRelayConnectionArgumentsargs) {// `args` is extracted from arguments context           }
@GraphQLField("manager")publicUserObjectTypegetManager(@GraphQLInUserObjectTypeemployee) {// `employee` is extracted from upstream 'source' element as input parameters are empty        }

Mutations

Mutation must be declared in the class marked as@GraphQLSchema annotation.Use@GraphQLMutation annotation to mark method as a GraphQL Mutation.Input method parameters are inputvariables.Method returned result is a mutation nested nodes.

// default value provider for an input mutation parameterpublicAddTodoIngetAddTodoInputDefaultValue() {AddTodoInaddTodoInput =newAddTodoIn();addTodoInput.setText("--- default text ---");returnaddTodoInput;    }@GraphQLMutation("addTodoMutation")public@GraphQLOut("todoEdge")TodoObjectType.TodoEdgeObjectTypeaddTodoMutation(@GraphQLIn("addTodoInput",defaultProvider ="getAddTodoInputDefaultValue")AddTodoInaddTodoInput,AddTodoIn2addTodoInput2) {// `addTodoInput` created based on input `variables`// `addTodoInput2` is skipped as it's not marked explicitly as `@GraphQLIn` parameter        }

Value for@GraphQLMutation is optional. If omitted - method name is used as a name of a mutation.@GraphQLOut is used to give a name for a mutation output type.All expected input variables have to be marked with@GraphQLIn annotation.To provide default value for an input parameter thedefaultProvider annotation field should point to the local class method (either static or regular).The return value of the method must be of the same type (derived type) as defined in mutation method signature.Also default value can be provided using SpEL expression:

@GraphQLField("manager")publicUserObjectTypegetManager(UserObjectTypeemployee,@GraphQLIn(value ="ids",defaultSpel ="T(java.util.Collections).EMPTY_SET")Set<String>ids) {// omitted for simplicity    }

Spring configuration

// there must be either `@ComponentScan` annotation defined for a schema base package// or all beans must be instantiated explicitly in configuration class@Configuration@ComponentScan(basePackages ="com.oembedler.moon.graphql.test.todoschema")publicstaticclassTodoSchemaConfiguration {// use as is@BeanpublicGraphQLSchemaBeanFactorygraphQLSchemaBeanFactory() {returnnewSpringGraphQLSchemaBeanFactory();        }// configuration can be customized depending on the case@BeanpublicGraphQLSchemaConfiggraphQLSchemaConfig() {GraphQLSchemaConfiggraphQLSchemaConfig =newGraphQLSchemaConfig();returngraphQLSchemaConfig;        }// use as is@BeanpublicGraphQLSchemaBuildergraphQLSchemaBuilder() {returnnewGraphQLSchemaBuilder(graphQLSchemaConfig(),graphQLSchemaBeanFactory());        }// use as is@BeanpublicGraphQLSchemaHoldergraphQLSchemaHolder() {returngraphQLSchemaBuilder().buildSchema(TodoSchema.class);        }    }

Executing queries async:

RxExecutionResultresult =GraphQLQueryExecutor                                             .create(graphQLSchemaHolder)                                             .query("{viewer{ id }}")                                             .execute();// work with execution result

Executing queries async with concurrent fields resolution(seeGraphQLQueryExecutor.forkJoinExecutorService() orGraphQLQueryExecutor.forkJoinExecutorService(int parallelism)):

GraphQLRxExecutionResultresult =GraphQLQueryExecutor.builder()                        .create(graphQLSchemaHolder)                        .forkJoinExecutorService()                        .query("{viewer{ id }}")                        .execute();

checkGraphQLQueryExecutor class to find more ways how to run queries.

Schema build process can be customized usingcom.oembedler.moon.graphql.engine.GraphQLSchemaConfig:

privateStringclientMutationIdName ="clientMutationId";privatebooleaninjectClientMutationId =true;privatebooleanallowEmptyClientMutationId =false;privateStringmutationInputArgumentName ="input";privateStringoutputObjectNamePrefix ="Payload";privateStringinputObjectNamePrefix ="Input";privateStringschemaMutationObjectName ="Mutation";privatebooleandateAsTimestamp =true;privateStringdateFormat ="yyyy-MM-dd'T'HH:mm'Z'";// there will be more config options added in the future

Protection Against Malicious Queries

Since typical GraphQL schemas contain recursive types and circular dependencies,clients are able to send infinitely deep queries which may have high impact on server performance.The library provides two mechanisms to protect your GraphQL server from malicious or too expensive queries.

Query Complexity Analysis

Query complexity analysis makes an estimation of the query complexityduring execution.The complexity isDouble number that is calculated according to the simple rule described below.Every field in the query gets a default score1.0 (includingGraphQLObjectType nodes).Thecomplexity of the query is the sum of all field scores.

publicIntegergetFirstDefaultValue() {return1;    }publicIntegergetLastDefaultValue() {return1;    }// `before`, `after`, `first`, `last` and `childScore` are avaliable in SpEL expression@GraphQLField@GraphQLComplexity("1 + first * #childScore")publicTodoConnectionObjectTypetodos(@GraphQLIn(value ="before")Stringbefore,@GraphQLIn(value ="after")Stringafter,@GraphQLIn(value ="first",defaultProvider ="getFirstDefaultValue")Integerfirst,@GraphQLIn(value ="last",defaultProvider ="getLastDefaultValue")Integerlast) {// implementation ommitted for the sake of simplicityreturntodoConnectionObjectType;    }

Note that above example hasGraphQLComplexity annotation value is a Spring SpEL expression.This annotation can be used to customize complexity calculation for a node.SpEL expression context has field input parameters andchildScore parameter which is sum of all child fields scores.

To setmaxQueryComplexity useGraphQLQueryExecutor:

GraphQLQueryExecutor.create(graphQLSchemaHolder).maxQueryComplexity(1500);

The query complexity algorithm isdynamic so typicalintrospection query doesn't have static permanent complexity -having more fields definitions, arguments and objects in a schema causes complexity to grow.

During execution when maximum query complexity reached - library throws anQueryComplexityLimitExceededRuntimeException exception.

Limiting Query Depth

Limiting query depth can be done by providing themaxQueryDepth argument to theGraphQLQueryExecutor:

GraphQLQueryExecutor.create(graphQLSchemaHolder).maxQueryDepth(4);

When maximum query depth is reached librarydoes not throw any exception but returnsnull for unresolved field(s).

Relay Support

Library adds abstractions for the Relay support.Please look at the tests forTodoSchema for an example.There are ways how to extend Relay classes to get custom behaviour.

Some tips:

@GraphQLObject("Todo")publicclassTodoObjectTypeextendsBaseObjectType {// fields definitions are omitted for clarity@GraphQLObjectpublicstaticclassTodoEdgeObjectTypeextendsEdgeObjectType<TodoObjectType> {// `EdgeObjectType` is generic class that can be extended to add custom behaviour          }@GraphQLObjectpublicstaticclassTodoConnectionObjectTypeextendsConnectionObjectType<TodoEdgeObjectType,PageInfoObjectType> {// `ConnectionObjectType` is generic class that can be extended to add custom behaviour        }}

Node interface (given schema uses proper hierarchy of objects):

// defined in library `relay` package@GraphQLInterface("Node")publicinterfaceRelayNode {@GraphQLID("id")@GraphQLNonNull@GraphQLDescription("GraphQL Relay global object unique identifier")StringgetId(RelayNoderelayNode);}

All custom objects implement that interface through intermediate base class (no need to implement default bahaviour in each class):

publicclassBaseObjectTypeimplementsRelayNode {@GraphQLIgnoreprivateStringid;@GraphQLID("id")@GraphQLNonNull@GraphQLDescription("Global object unique identifier")publicStringgetId(RelayNoderelayNode) {BaseObjectTypebaseObjectType = (BaseObjectType)relayNode;// `id` can be encoded into base64 if opaque value is requiredreturnbaseObjectType.id;    }}

Data resolver is defined in a root object query as follows:

@GraphQLObject("Root")publicclassRootObjectType {@GraphQLFieldpublicRelayNodenode(@GraphQLID@GraphQLNonNull@GraphQLIn("id")finalStringid) {// data resolver by global ID goes herereturnnull;    }    }

RelayConnectionArguments can be passed in multiple ways to a data resolver.

@GraphQLFieldpublicTodoConnectionObjectTypetodos(@GraphQLIn(value ="before")Stringbefore,@GraphQLIn(value ="after")Stringafter,@GraphQLIn(value ="first")Integerfirst,@GraphQLIn(value ="last")Integerlast) {}

Complex (nested) input objects are also supported.

publicclassRelayConnectionArguments {publicStringbefore;publicStringafter;publicIntegerfirst;publicIntegerlast;// ...    }@GraphQLFieldpublicTodoConnectionObjectTypetodos(@GraphQLIn("connArgs")RelayConnectionArguments) {// when query for a data arguments must be passed as embedded input argument `connArgs`// { ... todos(connArgs{first: 10}){...} ... }    }

Note using wrapper object for arguments make them surface from that wrapper node but not from input root.

In general the library does not force to build Relay compliant schemas -it's up to developer to decide if this compatibility should be maintained.

Contributions

Contributions are welcome.

Tips:

Acknowledgment

This implementation is based on the java reference implementation.For example theTodoSchema and the tests (among a lot of other things) are simply adapted to use with library code.

License

spring-graphql-common is licensed under the MIT License. SeeLICENSE for details.

graphql-java License

graphql-js License

About

Spring Framework GraphQL Library

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors2

  •  
  •  

Languages


[8]ページ先頭

©2009-2025 Movatter.jp