GraphQL, Middleware, and Mule
This post shows how middleware and GraphQL, specifically Mule, in this case, play quite well together.
Join the DZone community and get the full member experience.
Join For FreeI'm not sold on GraphQL, and this post is not going to go into detail about GraphQl or anything. This is a just small post to show how middleware and GraphQL, specifically Mule, in this case, play quite well together.
GraphQL and it's advocates tout how it can fetch multiple resources together and choreograph and combine data from Rest APIs, databases, SOAP services, etc. Sound familiar? It sounds like most middleware tools. But that's not what GraphQL does. It gives you the API layer to be able to do that, but not actually the middleware part to orchestrate and connect all the systems.
Basically, it gives you the API layer and then the technology implementation of it, such as GraphQL — Java gives you a concept of "resolvers." These resolvers you then need to code how to get your data for the specific fields requests in the GraphQL request.
Although in its infancy, it's just a lab project at the moment. Mulesoft has a minimal GraphQL router module that works quite nicely. Here is an example of a simple GraphQL schema we will use to demonstrate:
schema {
query: Query
}
type Query{
author(
name : String
): Author
}
# This is the simple description of a humble author
type Author {
#this is the name of the author.
name: String
books: [Book]
}
type Book {
name: String
}
A sample GraphIQL request:
{
author(name: "ryan") {
name
books{
name
}
}
}
Here is response received from Mule:
{
"data": {
"author": {
"name": "ryan",
"books": [
{
"name": "Getting Started with Mule Cloud Connect"
}
]
}
}
}
Here is the Mule configuration to implement the API:
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:core="http://www.mulesoft.org/schema/mule/core"
xmlns:scripting="http://www.mulesoft.org/schema/mule/scripting"
xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:graphql="http://www.mulesoft.org/schema/mule/graphql" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/graphql http://www.mulesoft.org/schema/mule/graphql/current/mule-graphql.xsd
http://www.mulesoft.org/schema/mule/scripting http://www.mulesoft.org/schema/mule/scripting/current/mule-scripting.xsd">
<http:listener-config name="api-httpListenerConfig">
<http:listener-connection host="0.0.0.0"
port="8083" protocol="HTTP">
</http:listener-connection>
</http:listener-config>
<graphql:config name="graphql-config" configName="mygraphql"
schemaLocation="schema/schema.graphqls" />
<flow name="console">
<http:listener config-ref="api-httpListenerConfig" path="/graphiql" />
<parse-template location="index.html" doc:name="Parse Template" />
<set-payload value="#[payload]" mimeType="text/html" />
</flow>
<flow name="graphql">
<http:listener config-ref="api-httpListenerConfig" path="/graphql" />
<graphql:router config-ref="graphql-config" payload="#[payload]" />
</flow>
<flow name="graphql:author">
<graphql:graphql-field-resolver
config-ref="graphql-config" fieldName="author" />
<scripting:execute engine="groovy">
<scripting:code>
payload = ['params' : attributes.getArguments(),
'fields' : attributes.getSelectionSet().get(),
'source' : 'person']
</scripting:code>
</scripting:execute>
<logger level="ERROR" message="Query params etc: #[payload]" />
<!-- Do query etc. if no field resolver needed it will just find it in
the payload. obviously needs to match field names exactly etc. -->
<logger level="INFO" message="#[payload]" />
<set-payload
value="#[output application/java --- {'name' : 'ryan', 'anotherfield' :'a field that will not get returned'}]" />
</flow>
<flow name="graphql:books">
<graphql:graphql-field-resolver
config-ref="graphql-config" fieldName="books" />
<scripting:execute engine="groovy">
<scripting:code>
payload = attributes.getSource()
</scripting:code>
</scripting:execute>
<!-- Use these details to actually lookup books from a store, rest api
or whatever. -->
<logger level="INFO" message="params etc: #[payload]" />
<set-payload
value="#[output application/java --- [{'name' :'Getting Started with Mule Cloud Connect'}]]" />
</flow>
</mule>
Basically, the module allows you to define flows for every field. It will route to the flow containing a field resolver matching the name of the field in the request. In the example we have requested: author, name and book, name.
You can define a field resolver for each of these fields if you wanted to. But as we typically will get the name for the author from one source and the name of the book from one source, a resolver for author and books is enough. As there are no field resolvers specifically defined for "name" in either case, the router will automatically select them from the payload returned in the flow as the default resolver. So in the author and books flow, both need to have a field called "name" in the payload in order to return the value.
As I said, the module is in its infancy and needs some work to add the proper attributes to the message for the fields requested and the query parameters for example. But they are available, as you can see in the mule configuration example. In the request, we request authors with name="ryan" that query parameter is available via attributes.getArguments(). The requested fields to return are handy, so we can modify our SQL queries to prevent over-fetching, for example, they are available via attributes.getSelectionSet()
As a graph is hierarchical, the router will execute each flow in order from top-down. So the response from the author flow will be the payload sent to the books flow, for example. So, at the moment, you have to make sure to copy and pass the correct arguments around that you need in each flow to fetch your data.
Also, as you can see, there is a simple static resource hosted also to provide the GraphIQL console for interacting and testing with the API. The full working project is on my Github, here.
Anyway, it's a promising module and will hopefully get the same attention as the APIKit router to make it more friendly. I'm working on some improvements myself for it.
Published at DZone with permission of Ryan Carter, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments