Morpheus GraphQL provides two way of type resolving.
- Values as resolvers: In this approach, you specify values
for the type definitions, where the resolvers are regular functions.
- Named resolvers: In this approach, we use the type class
ResolveNamed to define the
resolver for each type. More information on this approach can be
found in the next section.
As mentioned earlier, in this approach we use
to define the resolver function for each type. In this resolver definition,
each type also defines its dependency (identifier), which is used by the
compiler to provide a corresponding output resolution for certain input values.
That is, if we want to resolve a type as a field of another type, we must
specify a type dependency value for that particular type
instead of the type value. For a better illustration,
let's look at the following example:
Let's say we want to create a GraphQL app for
a blogging website where we can either retrieve
all posts or retrieve them by ID. Scheme definition for this
application would be as follows.
Now that we have type definitions, we can define their resolvers,
starting with type
Post. The following instance specifies that for each unique
we can resolve the corresponding
Post, where the post
title is retrieved by the post
Let's go to the next step and define a query resolver. Since the query does not
require an ID, we define its dependency with the unit type.
To resolve the
posts fields, we only get post ids and
pass them to the resolve function, which then resolves the
Post values by calling the
instance of the type
Post with those ids.
In the last step, we can derive the GraphQL application using
the data type
NamedResolvers by using a single constructor
NamedResolvers without any fields.
In the background, the function
deriveApp traverses the data types and calls their
own instances of
NamedResolver for each object and union type. In this way,
ResolverMaps (with type
Map TypeName (DependencyValue -> ResolveValue)) is derived that can
be used in GraphQL query execution.
As you can see, the
ResolverMaps derived in this way can be
merged if the types with the same name have the same GraphQL
kind and the same dependency.
Therefore, types in applications derived with
NamedResolvers can be safely extended,
which we will see in the next section.
Let's say there is another team that wants to use the
Posts application as well,
but also needs to provide
Authors information. The new application should
allow querying of all existing
Authors and extend the post type with the field
One way to address these new requirements would be to rewrite our old application,
but that will impact (or even break) the existing application. Here, named resolvers can
be of additional help to us, as
Apps derived with named resolvers can be merged.
We can define our
Authors app separately and then merge it with the existing one.
In the following code snippets we define the Author and Query types.
As you can see, we can query
authors, with each
Author having their fields
in the same manner as before, we can also provide their resolver implementation.
At this stage, we have already implemented Authors and Query and now we can also
start thinking about the Post Type.
First note, that the post type used in this app does not need to
be imported from the
App/Posts.hs. We can simply define our type
Post with the new
author and all other fields associated with the post type will be automatically
completed by the app
App/Posts.hs, after the merging.
Now we can start implementing the resolver for it.
It is of critical importance here, that the dependency of this type
is the same as the dependency of
App/Posts.hs. If the
argument of the function does not match, one of the implementations
will be unable to decode the argument during resolution and it will fail.
Since all resolvers are implemented, we can also derive the application.
Note that this application can be used as a standalone application, however
the standalone version can only display the information provided by the Authors,
i.e. the Post type will only have one field authors,
and in the query we can only access authors.
However, if we want to access information from both apps,
the next section will show us how to merge them.
The data type
App has a
Semigroup instance that allows to
join multiple apps together.
Since both the
Post type definitions have the same dependency
the interpreter safely merge these two apps where type
Post will be extended with new field