Headless Server Developer Manual / Version 2301
Table Of Contents
The standard GraphQL Java @fetch
directive
has been extended by CoreMedia to support the
Spring Expression Language.
This gains a lot of flexibility for implementing data fetching logic, often avoiding the need to extend a Java class with
corresponding properties.
As a simple example, assume you want to make the name
field of some object to be
available as is and, additionally, with all characters converted to upper case:
type SomeObjectType { name uppercaseName: @fetch(from: "name.toUpperCase()") }
The special
SpringEL variables
#this
and #root
are initially bound to the target object of the field.
Note that, according to
SpringEL semantics,
the #root
variable remains to be bound to this object during expression evaluation, while the #this
variable may change,
depending on expression context.
The following fields all fetch the same value:
name name2: @fetch(from: "name") name3: @fetch(from: "#root.name") name4: @fetch(from: "#this.name")
With the original @fetch
directive from GraphQL Java,
only the first, simple form is allowed, the "expression language" is restricted to simple identifiers.
The CoreMedia Headless Server @fetch
directive implements a strict superset.
In GraphQL, fields may take arguments. Inside the fetch expression, these are available as SpringEL variables of the same name:
type Query { add(x: Int!, y: Int): Int! @fetch(from: "#x + #y") }
Other SpringEL variables may be defined by adding Spring beans with the qualifier globalSpelVariables
.
Moreover, SpringEL variables may also be bound to functions. Such functions might help to
keep the SpringEL expressions short and concise. For example, in CaasConfig.java
,
a SpringEL function #first
is defined
with a static method from class SpelFunctions
. It retrieves the first element of a list,
or null if the list is itself null or empty:
@Bean @Qualifier("globalSpelVariables") public Method first() throws NoSuchMethodException { return SpelFunctions.class.getDeclaredMethod("first", List.class); }
A @fetch
directive utilizing this function may look like this:
authors: [CMTeasable] author: CMTeasable @fetch(from: "#first(authors)")
The same functionality might be expressed with a rather lengthy expression using the ternary (conditional) operator:
authors: [CMTeasable] author: CMTeasable @fetch(from: "authors?authors.length>0?authors[0]:null:null")
Note that SpringEL variables all share the same name space, so be aware of possible name clashes.
The GraphQL schema content-schema.graphql
contains many more examples
for Spring EL expressions.
When accessing settings or nested properties there are two ways to do so. Firstly, it is possible to access the properties via the Spring expressions:
@fetch(from: "structName?.pathSegmentA?.pathSegmentB?.propertyName")
Using this will however result in an error if one of the path segments or the property itself does not exist on the object. A more reliable way of accessing settings and properties would be to use the SettingsAdapter and the StructAdapter (see Section 4.8, “Adapter”) for access to these kinds of properties. They take care of existing properties, it is possible to query multiple properties at once and to pass them default values. Additionally, they provide the option to wrap a value in its path, which means that the adapter does not return the value directly, but instead wrapped in a hierarchical structure, representing the path.
@fetch(from: "@structAdapter.to(#root).getWrappedInStruct('structName', {'pathSegmentA','pathSegmentB','propertyName'}, 'defaultValue')"
The result would be something like:
{structName:{pathSegmentA:{pathSegmentB:{propertyName:propertyValue}}}}