In my prior example, I was creating a Graphene `ObjectType` of `Bucket` that had two class constants of `bucket_name` and `region` assigned an empty `String` Graphene `Scalar`.
On the surface, there wasn’t anything too complicated in declaring the `ObjectType ` – after all, it’s taking a fairly pythonic approach – but I was left with the question of how Graphene would know how to assign values to those constants.
from graphene import ObjectType, String class Bucket(ObjectType): bucket_name = String() region = String()
That brings me to resolvers. At it’s most basic, a `resolver` in GraphQL is a getter/setter function for the `ObjectType`. It is the instruction set, the logic, to get the data required to populate the type, and then to assign it to the appropriate attributes. It’s the component that says “This is how you get that thing.”
A static example
Let’s pretend for a minute that I have a very small AWS footprint that makes use of a single bucket, named `my_bucket` in the `us-east-1` region.
In that example, making a resolver for an implementation of `Bucket` becomes pretty simple.
def resolve_bucket( parent, info ): return Bucket( bucket_name = 'my_bucket', region = 'us_east_1' )
My needs are pretty straightforward here. I just need a function that returns the same thing every time – an instance of `Bucket` with attributes of `bucket_name` and `region` always equaling `my_bucket` and `us-east-1`.
First GraphQL resolver in Python done!
A better, dynamic example
A more realistic scenario is I have a lot of buckets in a lot of different regions, so I need to be able to do some sort of look up based on a set of given arguments. But, again, this is where I am just writing a Python function, so let’s just add some relevant arguments and hand-wave-y business logic to find what I need.
def resolve_bucket( parent, info, bucket_name, region ): record = record_lookup(bucket_name, region) if record is None: return Bucket() else: return Bucket( bucket_name = record.bucket_name, region = record.region )
This is still fairly accessible, but also clearly gives me more functionality. My function now has multiple arguments to perform lookups, and I’m also able to handle a scenario where my `record_lookup` capability is unable to find a match to the request and return an empty `Bucket`.
What this means for Resolvers in GraphQL
Let’s take a breath to stitch all of this together.
- I created an `ObjectType` by inheriting the Graphene class into a child class
- I assigned class constants to that class
- I have now written a function that returns an instance of that class with the attributes populated appropriately with some data.
The GraphQL spec, as we will see more and more, promises to do a lot of work for us. Many times, it needs us to write some stubby classes, assign them to each other, and it will do the work of handling execution order and return collection structuring, giving us nicely flexible and predictable and graph-based nested query results. But all of that auto-magical-ness strips away when we get to resolvers.
As I’ve show here, at it’s most basic a GraphQL resolver is just a function. But even our basic example makes several assumptions:
- I have implemented `record_loopup` somewhere
- `record_lookup` is able to reach out against something and do that lookup activity
- `record_lookup` pulls all of that together into a schema that I am able to translate back into a context that works for the `Bucket` attributes
It’s not hard to extrapolate this one method into a lot of moving parts and far more complex functionality.
But we can leave all of that calorie burn for another day, because while I now have a defined ObjectType
and a resolver
to build it for me, I am still missing an interface that binds those two things together.
And for that, I will need to build a Field.
One detail probably with noting
Anyone familiar with the Graphene docs will probably notice that I am skipping a few details here. One is the use of the default resolver. A second is the use of the “resolver in the class” method, thereby skipping the need for a Field
. Maybe a third is not getting into resolving fields individually.
These would all be valid approaches, and I plan to address them in a later post. I would hope, however, that seeing the steps that Graphene normal obscures explicitly laid out can help explain what’s happening under the hood.
One more thing…
Typically, this is where I see the first objections to GraphQL stand up – if I still have to write all of this logic, why go to the trouble of learning and thinking through a GraphQL interface and not just use a RESTful interface using stuff I already know?
I spent a little too much time taking up that topic in a separate post. You can read it here.
Leave a Reply