GraphQL Viewer
The GraphQL viewer automatically exposes an Apache Causeway domain object model as a GraphQL API. The viewer iterates over the domain services and domain objects, with their actions, properties and collections represented as fields within the graph. Actions that take parameters are mapped to fields with arguments.
The GraphQL viewer is implemented using Spring GraphQL project, which in turn is built on top of GraphQL Java. This also means that it automatically provides the GraphiQL interactive query tool.
Concepts
API Variants
The GraphQL viewer supports 3 different API variants, controlled by the causeway.viewer.graphql.api-variant configuration property:
-
QUERY_ONLY
Exposes only a Query API, of properties, collections and safe (query-onl) actions. Any actions that mutate the state of the system (in other words are idempotent or non-idempotent are excluded from the API, as is the ability to set properties.
-
QUERY_AND_MUTATIONS
Exposes an API with Query for query/safe separate queries and field access, with mutating (idempotent and non-idempotent) actions and property setters instead surfaced as Mutations, as per the GraphQL spec.
-
QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT
This variant exposes an API with both Query and Mutations, but relaxes the constraints for the Query API by also including idempotent and non-idempotent actions and property setters. The actions are also available through as Mutations (same as with
QUERY_AND_MUTATIONS
API variant).Examples of using this API variant are provided in the Queries that are also Mutations section, below.
Be aware that the resultant API is not compliant with the rules of the GraphQL spec; in particular, it violates 2.3 Operations which states: "a query [is] a read‐only fetch";
In summary, the API variant therefore relates to whether and how mutating (non-safe) actions are represented.
Rich vs Simple Schemas
The viewer can represent the domain model as either a "rich" GraphQL schema, or a "simple" GraphQL schema; or indeed both can be supported ath the same time.
The rich schema represents all the behaviour and structure of the Causeway domain model:
-
hidden, disabled, validate
-
choices, autoComplete, defaults
-
obtain ("get") the value
In other words, it exposes not only the core data/behaviour but also of the supporting methods.
The simple schema represents just the core data/behaviour, but does not include the supporting method facets.
With the rich schema, the application logic and business domain logic remains the concern of the server, with the calling client app responsible only for presentation logic. In fact, the rich schemas is so rich that one could in theory implement a fully generic UI client.
With the simple schema, although the main business domain logic remains the concern of the server, the application logic moves to the client. For example, if there is a drop-down list box, the client needs to know how to obtain the list of choices. But the benefit of the simple schema is, well, that it is simpler; in other words more "intuitive" and less "verbose".
The causeway.viewer.graphql.schema-style configuration property specifies which schema (or both) is supported:
-
SIMPLE_ONLY
- exposes only the "simple" schema -
RICH_ONLY
- exposes only the "rich" schema -
SIMPLE_AND_RICH
- exposes both the simple and rich schemas, the top-level query for each residing under either thesimple
orrich
field respectively.For the top-level mutation, the "simple" schema is used.
-
RICH_AND_SIMPLE
- also exposes both the simple and rich schemas, the top-level query for each residing under either thesimple
orrich
field respectively.For the top-level mutation, the "rich" schema is used.
If only the simple or the rich schema is enabled (SIMPLE_ONLY
or RICH_ONLY
), then the set of types defined by each of these schemes is available at the root query.
If both schemas are enabled (SIMPLE_AND_RICH
or RICH_AND_SIMPLE
), the these each reside under a top-level field, by default "simple" or "rich".
The name of these fields can be configured to something else if required using causeway.viewer.graphql.top-level-field-name-for-simple and causeway.viewer.graphql.top-level-field-name-for-rich configuration properties.
Example
How the viewer works is probably most easily explained by an example. The diagram below shows a simple domain (in fact, this is the domain used by the GraphQL viewer’s own tests):
GraphQL distinguishes queries and mutations, so let’s look at each.
We’ll assume here that the SIMPLE_AND_RICH
schema style is in use.
GraphQL also defines the notion of subscriptions; the GraphQL viewer currently has no support for these. |
Queries
Queries most often start at a domain service.
In the example above, these would be Departments
, DeptHeadRepository
, or Staff
.
To list all Department
s, we can submit this query using either the "rich" schema or "simple" schema:
Rich schema | Simple schema |
---|---|
|
|
1 | specify schema style |
2 | domain service |
3 | action name |
4 | invokes the action |
5 | returning a list of Department s |
6 | gets (accesses) the name property of each returned Department |
7 | also gets (accesses) the staffMembers collection of each returned Department , returning a list of StaffMember s |
8 | gets the name prperty for each returned StaffMember |
9 | returns the internal id and logicalTypeName of each StaffMember .
Together, these make up the Bookmark of the domain object. |
Queries can also include parameters.
Rich schema | Simple schema |
---|---|
|
|
The above is an example of invoking an action on a (singleton) domain service, but this works equally well on domain entities/view models once retrieved. More on this below.
Supporting Metadata
If you use the "rich" schema, then as well as accessing properties and collections and invoking (safe) actions, the GraphQL viewer also allows access to the usual supporting metadata. For example:
Rich schema | Simple schema |
---|---|
|
Not supported by simple schema. |
1 | whether this action is disabled |
2 | whether the property of the resultant object is hidden |
Similarly, there are fields for action parameters:
-
validate
- is the proposed action parameter valid? -
disable
- is the action or action parameter disabled? -
choices
- for an action parameter, are their choices? -
autoComplete
- for an action parameter, is there an auto-complete? -
default
- for an action parameter, is there a default value?
There are also similar fields for properties:
-
validate
- is the proposed value of the property valid? -
disable
- is the property disabled? -
choices
- for a property, are their choices? -
autoComplete
- for a property , is there an auto-complete?
The Meta field/type
The _meta
field provides access to additional information about the domain object.
Its full list of fields are:
-
logicalTypeName
andid
; these are equivalent to the Bookmark of the domain object -
version
(if an entity and available) -
title
(as per the title() supporting method) -
icon
(as per the icon, normally the associated.png
file) andgrid
(as per the layout, normally the associated.layout.xml
file )These can only be downloaded if configured, see resources section below.
-
cssClass
(as per the cssClass() supporting method)
There is also one additional field, saveAs
; this is discussed in the Test Support section.
Queries that lookup a Domain Object
Most queries will start with a domain service, but it is also possible to define a query that starts with a "lookup" of existing domain object:
Rich schema | Simple schema |
---|---|
|
|
1 | logical type name of the domain object |
2 | identifier of the domain object instance |
The next section explains how use mutations to change the state of the system.
Mutations
Actions that mutate the state of the system (with idempotent or non-idempotent @Action#semantics) are exposed as mutations. Editable properties are also exposed as mutations.
IF the action is on a domain service, then the target is implicit; but if the action is on a domain object — and also for properties — then the target domain object must be specified.
For example, to invoke a mutating action on a domain service:
Rich schema | Simple schema |
---|---|
|
|
1 | derived from the logical type name of the domain service, and the action Id. |
For example, to invoke a mutating action on a domain object
Rich schema | Simple schema |
---|---|
|
|
1 | derived from the logical type name of the domain object, and the action Id. |
2 | the object argument specifies the target object |
Or, to set a property on a domain object:
Rich schema | Simple schema |
---|---|
|
|
1 | derived from the logical type name of the domain object, and the property Id. |
2 | the _target argument specifies the target object |
3 | property setters are void , so as a convenience the mutator instead returns the target object. |
Queries that are also Mutations
According to the GraphQL specification, queries should be read-only; they must not change the state of the system.
Enabling the QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT
API variant (also mentioned above) relaxes this rule, allowing actions to be invoked that do change the state of the system, and — indeed — allowing properties to be modified.
This is done through these additional fields:
-
invokeIdempotent
- to invoke an action whose action semantics are idempotentAs specified by @Action#semantics.
-
invokeNonIdempotent
- to invoke an action whose action semantics are non-idempotent -
set
- to modify a property.
For example, to invoke an action on a domain service:
Rich schema | Simple schema |
---|---|
|
|
Or, to find a domain object and then invoke a mutating action on it:
Rich schema | Simple schema |
---|---|
|
|
Or, similarly to find a domain object and then set a property afterwards:
Rich schema | Simple schema |
---|---|
|
Not supported by simple schema |
Resources (Blobs, Clobs, Layouts, Icons)
Rather than returning the values of Blobs and Clobs inline within a response, instead the GraphQL viewer renders these as a URL to a resource controller. The client can then make a second call to this endpoint using a simple HTTP(s) GET.
The same approach is used for both simple and rich schemas.
For example:
Rich schema | Simple schema | ||
---|---|---|---|
|
|
This will result in a response (for the rich schema) such as:
{
"data" : {
"rich" : {
"university_dept_Staff" : {
"findStaffMemberByName" : {
"invoke" : {
"results" : {
"photo" : {
"get" : {
"bytes" : "///graphql/object/university.dept.StaffMember:123/photo/blobBytes"
}
}
}
}
}
}
}
}
}
The simple schema’s response is very similar.
The viewer does not currently provide any way to update Blobs or Clobs. One option is to implement a custom controller that the client can post to, analogous to the in-built resource controller. |
The meta field mentioned earlier also allows the icon
and grid
(layout) files to be downloaded in a similar way:
Rich schema | Simple schema | ||
---|---|---|---|
|
|
1 | URL to download the icon (typically a .png file) |
2 | URL to download the grid layout (typically the .layout.xml file) |
Because the resource controller exposes information directly, these fields are suppressed by default. To enable, use the causeway.viewer.graphql.resources.response-type configuration property. If you do this, then you should also make sure that the resource controller is made secure in some appropriate fashion.