Specification: GraphQL for MicroProfile Version: 1.0-M6 Status: Draft Release: January 31, 2020 Copyright (c) 2020 Contributors to the Eclipse Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Introduction to MicroProfile GraphQL
About GraphQL
GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL interprets strings from the client, and returns data in an understandable, predictable, pre-defined manner. GraphQL is an alternative, though not necessarily a replacement for REST.
GraphQL was developed internally by Facebook in 2012 before being publicly released in 2015. Facebook delivered both a specification and a reference implementation in JavaScript.
On 7 November 2018, Facebook moved the GraphQL project to the newly-established GraphQL foundation, hosted by the non-profit Linux Foundation. This is a significant milestone in terms of industry and community adoption. GraphQL is widely used by many customers.
-
More info: https://en.wikipedia.org/wiki/GraphQL
-
Home page: https://graphql.org/
-
Specification: https://facebook.github.io/graphql/draft/
Why GraphQL
The main reasons for using GraphQL are:
-
Avoiding over-fetching or under-fetching data. Clients can retrieve several types of data in a single request or can limit the response data based on specific criteria.
-
Enabling data models to evolve. Changes to the schema can be made so as to not require changes on existing clients, and vice versa - this can be done without a need for a new version of the application.
-
Advanced developer experience:
-
The schema defines how the data can be accessed and serves as the contract between the client and the server. Development teams on both sides can work without further communication.
-
Native schema introspection enables users to discover APIs and to refine the queries on the client-side. This advantage is increased with graphical tools such as GraphiQL and GraphQL Voyager enabling smooth and easy API discovery.
-
On the client-side, the query language provides flexibility and efficiency enabling developers to adapt to the constraints of their technical environments while reducing server round-trips.
-
GraphQL and REST
GraphQL and REST have many similarities and are both widely used in modern microservice applications. The two technologies also have some differences.
REST stands for "Representational State Transfer". It is an architectural style for network-based software specified by Roy Fielding in 2000 in a dissertation defining 6 theoretical constraints:
-
uniform interface
-
stateless
-
client-server
-
cacheable
-
layered system
-
code on demand (optional).
REST is often implemented as JSON over HTTP, but REST is fundamentally technically agnostic to data type and transport; it is an architectural style. In particular, it doesn’t require to use HTTP. However, it recommends using the maximum capacity of the underlying network protocol to apply the 6 basic principles. For instance, REST implementations can utilize HTTP semantics with a proper use of verbs (POST, GET, PUT, PATCH, DELETE) and response codes (2xx, 4xx, 5xx).
GraphQL takes its roots from a Facebook specification published in 2015. As of this date, GraphQL has been subject to 5 releases:
-
June 2018
-
October 2016
-
April 2016
-
October 2015
-
July 2015
According to it’s definition: "GraphQL is a query language for describing the capabilities and requirements of data models for client‐server applications."
Like REST, GraphQL is independent from particular transport protocols or data models:
-
it does not endorse the use of HTTP though in practice, and like REST, it is clearly the most widely used protocol,
-
it is not tied to any specific database technology or storage engine and is instead backed by existing code and data.
What makes GraphQL different?
In practice, here are the main differentiating features of GraphQL compared to REST:
-
schema-driven: a GraphQL API natively exposes a schema describing the structure of the data and operations (queries and mutations) exposed. This schema acts as a contract between the server and its clients. In a way GraphQL provides an explicit answer to the API discovery problem where REST relies on the ability of developers to properly use other mechanisms such as HATEOS and/or OpenAPI,
-
single HTTP endpoint: a typical GraphQL API is made of a single endpoint and access to data and operations is achieved through the query language. In a HTTP context, the endpoint is defined as a URL and the query can be transported as a query string (GET request) or in the request body (POST request),
-
flexible data retrieval: by construction the query language enables the client to select the expected data in the response with a fine level of granularity, thus avoiding over- or under-fetching data,
-
reduction of server requests: the language allows the client to aggregate the expected data into a single request,
-
easier version management: thanks to the native capabilities to create new data while deprecating old ones,
-
partial results: partial results are delivered by the GraphQL server in case of errors. A GraphQL result is made of data and errors. Clients are responsible for processing the partial results,
-
low coupling with HTTP: GraphQL does not try to make the most of HTTP semantics. Queries can be made using GET or POST requests. The HTTP result code does not reflect the GraphQL response,
-
challenging authorization handling: an appropriate data access authorization policy must be defined and implemented to counter the extreme flexibility of the query language. For example, one client may be authorized to access some data that others are not,
-
challenging API management: most API management solutions are based on REST capabilities and allow for endpoint (URL-based) policies to be established. GraphQL API has a single entry point. It may be necessary to analyze the client request data to ensure it conforms to established policies. For example, it may be necessary to validate mutations or to prevent the client from executing an overly complex request that would crash the server.
GraphQL and Databases
GraphQL is about data query and manipulation but it is not a database technology:
-
It is a query language for APIs,
-
It is database and storage agnostic,
-
It can be used in front of any kind of backend, with or without a database.
One of GraphQL’s strength is its multi-datasource capability enabling a single endpoint to aggregate data from various sources with a single API.
MicroProfile GraphQL
The intent of the MicroProfile GraphQL specification is provide a "code-first" set of APIs that will enable users to quickly develop portable GraphQL-based applications in Java.
There are 2 main requirements for all implementations of this specification, namely:
-
Generate and make the GraphQL Schema available. This is done by looking at the annotations in the users code, and must include all GraphQL Queries and Mutations as well as all entities as defined either explicitly by annotations or implicitly as the response type or argument(s) of Queries and Mutations.
-
Execute GraphQL requests. This will be in the form of either a GraphQL Query or Mutation. As a minimum the specification must support executing these requests via HTTP.
GraphQL Entities
Entities are the objects used in GraphQL. They can be:
-
Scalars, or simple objects ("scalars" in GraphQL terminology),
-
Enumerable types (similar to Java Enum),
-
Complex objects that are composed of scalars or other objects or enums or a combination of these.
Scalars
According to the GraphQL documentation a scalar has no sub-fields, and all GraphQL implementations are expected to handle the following scalar types:
-
Int
- which maps to a Javaint
/Integer
. -
Float
- which maps to a Javafloat
/Float
ordouble
/Double
. -
String
- which maps to a JavaString
. -
Boolean
- which maps to a Javaboolean
/Boolean
. -
ID
- which is a specialized type serialized like aString
. Usually, ID types are not intended to be human-readable.
Note that an ID scalar must map to a Java String
, numerical primitive long
and int
or their
object equivalents (Long
, Integer
), or a java.util.UUID
- anything else is considered a
deployment error.
GraphQL allows implementations to define additional scalars. MicroProfile GraphQL implementations are required to handle the following additional scalar types:
-
Short
- which maps to a Javashort
/Short
. -
Long
- which maps to a Javalong
/Long
. -
Char
- which maps to a Javachar
,Character
. -
Byte
- which maps to a Javabyte
/Byte
. -
BigInteger
- which maps to a JavaBigInteger
. -
BigDecimal
- which maps to a Java BigDecimal`. -
Date
- which maps to a Javajava.time.LocalDate
. -
Time
- which maps to a Javajava.time.LocalTime
. -
DateTime
- which maps to a Javajava.time.LocalDateTime
.
Implementations may define additional custom scalars beyond those listed above.
Numbers
All number scalars (Int, Double, Float, Short, Long, Byte, BigInteger and BigDecimal) can be formatted
using the @NumberFormat
or alternatively the JSON-B annotation @JsonbNumberFormat
(except that @JsonbNumberFormat
is not valid on TYPE_USE
)
In the case that a property has both @NumberFormat
and @JsonbNumberFormat
, the GraphQL annotation (@NumberFormat
) takes priority.
When formatting is added to a number type, the formatted result will be of type String.
Example:
1
2
@JsonbNumberFormat(value = "¤000",locale = "en_ZA")
private Short formattedShortObject;
will result in a formatted amount (South African Rand) in the result:
1
2
3
4
5
6
7
{
"data": {
"testScalarsInPojo": {
"formattedShortObject": "R123"
}
}
}
List example:
1
2
3
4
5
6
@Mutation
public SuperHero trackHeroLongLat(@Name("name") String name,
@Name("coordinates") List<List<@NumberFormat("00.0000000 'longlat'") BigDecimal>> coordinates) throws UnknownHeroException {
// Here add the tracking
return superHero;
}
Dates
By default the date related scalars (DateTime, Date, and Time) will use a ISO format.
-
yyyy-MM-dd'T'HH:mm:ss'Z'
for DateTime -
yyyy-MM-dd
for Date -
HH:mm:ss
for Time
By adding the @DateFormat
annotation, or alternatively JSON-B annotation @JsonbDateFormat
, a user can change the format. However, @JsonbDateFormat
does not
support usage on TYPE_USE
.
In the case that a property has both @DateFormat
and @JsonbDateFormat
, the GraphQL annotation (@DateFormat
) takes priority.
Enumerable types
GraphQL offers enumerable types similar to Java enum
types.
In order for an enum to be defined in the GraphQL schema, it must meet at least one of the following criteria:
-
It must be the return type or parameter (optionally annotated with
@Name
) of a query or mutation method, -
It must be annotated with
@Enum
The implementation will produce the GraphQL enum
type in
the schema. For example:
1
2
3
4
5
6
7
8
9
@Type
public class SuperHero {
private ShirtSize tshirtSize; // public getters/setters, ...
@Enum("ClothingSize")
public enum ShirtSize {
S, M, L, XL
}
}
The implementation would generate a schema that would include:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum ClothingSize {
L
M
S
XL
}
type SuperHero {
#...
tshirtSize: ClothingSize
#...
}
input SuperHeroInput
#...
tshirtSize: ClothingSize
#...
}
#...
When using an enumerated type, it is considered a validation error when the client enters a value that is not included in the enumerated type.
Complex objects
In order for an entity class to be defined in the GraphQL schema, it must meet at least one of the following criteria:
-
It must be the return type or parameter (optionally annotated with
@Name
) of a query or mutation method, -
It must be annotated with
@Type
, -
It must be annotated with
@Input
Any Plain Old Java Object (POJO) can be an entity. No special annotations are required. Implementations of MicroProfile GraphQL must use JSON-B to serialize and deserialize entities to JSON, so it is possible to further define entities using JSON-B annotations.
If the entity cannot be serialized by JSON-B, the implementation must return in an internal server error to the client.
Types vs Input
GraphQL differentiates types from input types. Input types are entities that are sent by the client as arguments to queries or mutations. Types are entities that are sent from the server to the client as return types from queries or mutations.
In many cases the same Java type can be used for input (sent from the client) and output (sent to the client), but there are cases where an application may need two different Java types to handle input and output.
The @Type
annotation is used for output entities while the @Input
annotation is used for input entities.
Normally these annotations are unnecessary if the type can be serialized and/or deserialized by JSON-B, and if the type
is specified in a query or mutation method. These annotations can be used to specify the name of the type in the GraphQL
schema; by default, the entity name in the schema will be the same as the simple class name of the entity type for
output types; for input types, the simple class name is used with "Input" appended. Thus, an entity class named
com.mypkg.Tree
would create a GraphQL schema type called "Tree" and an input type called "TreeInput".
Java interfaces as GraphQL entity types
It is possible for entities (types and input types) to be defined as a Java interfaces. In order for JSON-B to
deserialize an interface, the interface may need a JsonbDeserializer
in order to instantiate a concrete type.
GraphQL interfaces
GraphQL interfaces are very similar in concept to Java interfaces, in that other types may implement an interface. This
allows the GraphQL schema to better align with the Java application’s model and allows clients to retrieve the same data
(fields) on multiple different entity types. GraphQL interfaces are created with a Java interface type is annotated
with @Interface
. The MP GraphQL implementation must then generate a schema where every class in the application that
implements that Java interface must