Have it your way with MicroProfile GraphQL!
Open Liberty 20.0.0.6 introduces a new feature, MicroProfile GraphQL, which enables developers to quickly and easily write GraphQL applications with MicroProfile.
If you’re here reading about GraphQL in Open Liberty, then chances are that you already know a bit about GraphQL. In short, GraphQL is a remote data access API that addresses issues like under-fetching and over-fetching that are inherent in most RESTful applications. It allows clients to specify the exact data they want - to "have it their way". If you would like to learn more about GraphQL, I recommend checking out the tutorial at GraphQL.org.
GraphQL applications use a schema that acts as a description of the data provided by the server and as a contract between the client and server. Most GraphQL programming models require developers to dual-maintain their schema and the application code that supports it. MicroProfile GraphQL takes a "code first" approach which allows developers to write Java code using annotations to mark GraphQL schema elements, and then the MicroProfile GraphQL implementation generates the schema at runtime.
The Open Liberty feature Microprofile GraphQL (mpGraphQL-1.0
) implements the
MicroProfile GraphQL 1.0 specification.
In this blog post, we’ll explore how easy it is to create GraphQL applications using this sample.
Setup
To set up your Maven environment for developing a MP GraphQL application, you’ll need a dependency like:
<dependency>
<groupId>org.eclipse.microprofile.graphql</groupId>
<artifactId>microprofile-graphql-api</artifactId>
<version>1.0.2</version>
<scope>provided</scope>
</dependency>
I recommend using the Liberty Maven plugin, but it’s not strictly necessary so long as you generate a Web Archive (WAR) file for deployment.
When configuring the Liberty server for deployment, make sure that the featureManager
element in the server.xml
file
contains the mpGraphQL-1.0
feature. For example:
<server>
<featureManager>
<feature>mpGraphQL-1.0</feature>
<feature>mpMetrics-2.3</feature>
</featureManager>
<!-- ... -->
</server>
The mpMetrics-2.3
feature is not required, but will track the number of GraphQL query/mutation invocations and
cumulative time when enabled.
Now that we’ve got things set up, let’s look at the code.
Coding a MicroProfile GraphQL application
A MicroProfile GraphQL application should have at least one root-level query and/or mutation. To create a query or
mutation, you start with a public Java class annotated with @GraphQLApi
. Then you add public methods that are
annotated with @Query
or @Mutation
for query or mutation, respectively. The query/mutation methods must always
return a non-void type. These methods can return simple types like String
, int
, double
, boolean
, etc. which will
be mapped to GraphQL scalars such as String
, Int
, Float
, Boolean
, etc. Alternatively, these methods could return
custom Java types - the types would be mapped to GraphQL types and defined in the generated schema. For more details, see the MicroProfile GraphQL 1.0.2 API Docs.
For example, in the
sample application, the currentConditions
query method returns a type called Conditions
- that type has properties
such as temperatureF
, temperatureC
, precipitationType
, weatherText
, etc. The following schema is generated
from the query and its return value:
type Conditions {
dayTime: Boolean!
epochTime: BigInteger!
hasPrecipitation: Boolean!
"ISO-8601"
localObservationDateTime: DateTime
location: String
precipitationType: PrecipType
temperatureC: Float!
temperatureF: Float!
weatherText: String
wetBulbTempF: Float!
}
"Query root"
type Query {
currentConditions(location: String): Conditions
currentConditionsList(locations: [String]): [Conditions]
}
...
The currentConditions
query has an argument, called location
. In the Java code, the argument is represented by a
method parameter. Like output types, arguments/parameters can be simple Java types (mapping to GraphQL scalars) or
custom types, which would be mapped to input types in the generated schema.
When mapping custom types, it is possible to change the name used in the schema by using the @Name
annotation. For example, if we wanted to change the schema to display tempInFahrenheit
instead of temperatureF
, we
could just add @Name("tempInFahrenheit")
to the temperatureF
field in the Conditions
class.
If your application uses JSON-B, then @JsonbProperty
, @JsonbDateFormat
, and @JsonbNumberFormat
annotations can be
used instead of @Name
, @DateFormat
or @NumberFormat
. When both sets of annotations are used, the annotations from
the MicroProfile GraphQL APIs take precedence over the JSON-B APIs when used for schema generation or query/mutation execution.
Another useful annotation is @Source
. This annotation can be used to add a field to an entity object that might be
expensive to look up or calculate, and so you might not want to spend the resources on the server side to compute that
field when the client doesn’t want it anyway. Here’s an example from the sample:
public double wetBulbTempF(@Source @Name("conditions") Conditions conditions) {
// TODO: pretend like this is a really expensive operation
// ...
return conditions.getTemperatureF() - 3.0;
}
This example is a little contrived, but it shows us that the wetBulbTempF
field will only be computed if the client
requests that field. This method is in a class annotated with @GraphQLApi
(in this example, WeatherService
) and it
contains a parameter annotated with @Source
that takes the entity object, Conditions
. When a client issues a query
or mutation that would return the Conditions
entity, and that query/mutation specifies the wetBulbTempF
field, the wetBulbTempF(Conditions conditions)
method is invoked by the GraphQL implementation, passing in the
Conditions
object that was returned from the query/mutation method.
Running the MicroProfile GraphQL app
To run and test the GraphQL application, you simply need to deploy it as a WAR file. The Liberty Maven
Plugin makes it easy to build, deploy, and test using Apache Maven. After you have cloned the sample from GitHub
(git clone [email protected]:OpenLiberty/sample-mp-graphql.git
) or downloaded the source ZIP file, just run:
mvn clean package liberty:run
This builds, packages, and deploys the GraphQL application to the latest Open Liberty server runtime and starts the
server and app. Then you can use the pre-packaged GraphiQL HTML interface to send queries or mutations at:
http://localhost:9080/mpGraphQLSample/graphiql.html
Here are a few sample queries and mutations that you could use to get started - you may see some interesting results:
#Temperature (Fahrenheit) for Las Vegas
query LasVegas {
currentConditions(location: "Las Vegas") {
temperatureF
}
}
#Is it really always sunny in Philadelphia?
query SunnyInPhilly {
currentConditions(location: "Philadelphia") {
weatherText
}
}
# Weather conditions for three locations - one roundtrip
query threeLocations {
atlanta: currentConditions(location: "Atlanta") {
hasPrecipitation
temperatureF
weatherText
precipitationType
}
newyork: currentConditions(location: "New York") {
hasPrecipitation
temperatureF
weatherText
precipitationType
}
chicago: currentConditions(location: "Chicago") {
hasPrecipitation
temperatureF
weatherText
precipitationType
}
}
# See partial results when one portion of the query fails
query fourLocations {
atlanta: currentConditions(location: "Atlanta") {
hasPrecipitation
temperatureF
weatherText
precipitationType
wetBulbTempF
}
nowhere: currentConditions(location: "Nowhere") {
hasPrecipitation
temperatureF
weatherText
precipitationType
}
newyork: currentConditions(location: "New York") {
hasPrecipitation
temperatureF
weatherText
precipitationType
}
chicago: currentConditions(location: "Chicago") {
hasPrecipitation
temperatureF
weatherText
precipitationType
wetBulbTempF
}
}
# Reset the stored weather conditions
mutation {
reset
}
Authorizing access to certain queries/mutations
It may be necessary to restrict access to certain queries/mutations to certain authenticated users. While it is not part
of the MicroProfile GraphQL 1.0 specification (it is under consideration for a future version of the spec), Open Liberty
makes authorization checks possible by using the @DenyAll
, @PermitAll
, and @RolesAllowed
annotations. These
annotations must be placed on the class or method of classes annotated with @GraphQLApi
.
When implementing authorization with MicroProfile GraphQL, you need to enable the appSecurity-3.0
(or appSecurity-2.0
) feature in the
server configuration. You also need to set up the user registry and web container metadata for authentication and
authorization.
In the sample, we use the basic user registry which defines two users, one for each of two roles:
<basicRegistry id="basic" realm="sample-mp-graphql">
<user name="user1" password="user1pwd" />
<user name="user2" password="user2pwd" />
<group name="Role1">
<member name="user1"/>
</group>
<group name="Role2">
<member name="user2"/>
</group>
</basicRegistry>
This means that user1
is part of Role1
and user2
is part of Role2
. The web.xml
declares these roles, and also sets up
form-based authentication so that, when the Application Security feature is enabled, clients are prompted to log in using a
web-based form before accessing the GraphiQL HTML page. It also allows the application to prevent users other than
those in Role2
to invoke the reset
mutation method:
@RolesAllowed("Role2")
@Mutation
@Description("Reset the cached conditions so that new queries will return newly randomized weather data." +
"Returns number of entries cleared.")
public int reset() {
int cleared = currentConditionsMap.size();
currentConditionsMap.clear();
return cleared;
}
Integration with MicroProfile Metrics
If you enable the mpMetrics-2.3
feature with mpGraphQL-1.0
, Open Liberty tracks the number of times a particular
query or mutation method is invoked—and the cumulative time spent in that method. These metrics can be useful for
determining what data is being accessed, how often, and where time is spent in execution.
Metrics collection and reporting for GraphQL applications is not mentioned in either the MicroProfile GraphQL 1.0 spec or the
MicroProfile Metrics 2.3 spec, so the actual stats are collected and reported under the "vendor" category. To see these stats,
you can browse to:
http://localhost:9080/metrics/vendor
The stats are prefixed with vendor_mp_graphql_
and should look something like this:
# TYPE vendor_mp_graphql_Query_currentConditions_total counter
vendor_mp_graphql_Query_currentConditions_total 27
# TYPE vendor_mp_graphql_Query_currentConditions_elapsedTime_seconds gauge
vendor_mp_graphql_Query_currentConditions_elapsedTime_seconds 0.10273818800000001
# TYPE vendor_mp_graphql_Conditions_wetBulbTempF_total counter
vendor_mp_graphql_Conditions_wetBulbTempF_total 4
# TYPE vendor_mp_graphql_Conditions_wetBulbTempF_elapsedTime_seconds gauge
vendor_mp_graphql_Conditions_wetBulbTempF_elapsedTime_seconds 0.031866015000000004
# TYPE vendor_mp_graphql_Mutation_reset_total counter
vendor_mp_graphql_Mutation_reset_total 3
# TYPE vendor_mp_graphql_Mutation_reset_elapsedTime_seconds gauge
vendor_mp_graphql_Mutation_reset_elapsedTime_seconds 0.007540145000000001
Summary
GraphQL is a powerful and popular query language for remote data access. MicroProfile GraphQL makes it easy to develop GraphQL applications in Java. And now you use GraphQL in Open Liberty!