openapi-to-graphql
openapi-to-graphql copied to clipboard
Custom resolvers - looking for guidance
Hi guys,
I'm looking for some guidance in how to implement a custom resolver - if indeed that is the right tool for the job!
I've used OtG
as a library to generate a GraphQL
schema over our REST Api. In V1
of our API we have an endpoint that returns an array of order
objects and their associated order items
. In terms of performance this has proved to be sub-optimal due the amount of data that can be returned. In V2
of our API we have one endpoint that returns an array of order
's and another that returns an array of order item
's for a given order.
When using GraphQL
, we want the user to be able make a single request that specifies order
's and order item
's combined. I can think of a couple of options I might have.
-
Implement a
custom resolver
function that produces the correct response by combing the results of calling the 2 endpoints. -
Create a further
Graphql schema
with something likeApollo
that would give me total control of the schema and pass them both tocreateGraphQlSchema
So my questions are:
Are these the correct options to consider?
If I go down the custom resolver
route - how do I know if the user requested the order item
's be included so I don't make the request for order item
's unnecessarily. I get passed the following objects: obj, args, context, info in a custom resolver
but I can't see how they help.
Example of potential query:
ordersV2(page: 10) {
data {
id
guid
totalCost
orderNumber
orderDate
items {
id
itemName
}
}
}
Hi!
~~I don't completely understand the situation. Are order lines
and order items
equivalent? That seems to be the suggestion...~~
If I understand correctly, the V1
API has one endpoint that contains all the needed data and the V2
API has two endpoints and your goal is to create a custom resolver that will use both endpoints to get the needed data. Is this correct?
If that is correct, then are the two endpoints in the V2
API interrelated (shares similar inputs or the data from one API call is used to call the next)? If that is the case, I would actually heavily recommend trying to use our link feature. This feature is built around this use case, where one API call shares the same inputs as another or naturally leads to another.
You could also implement a custom resolver too but this is a little bit more fiddly because you will also need to mess around with the parameters and schemas in the OAS to "make room" for the order items
call. Additionally, you will not be able to know if the user requested the order item
. By using the link feature, you will not have to worry about either one of these problems.
If you still want to use a custom resolver, I can go more in depth. Assuming you want to expand upon what is currently returned by the ordersV2
field by providing the items
field, you will need to go into the order
schema and append the order item
schema. This will change the shape of the generated schema so that you can return items
.
If the inputs for order item
are different from those for order
, then you will need to change the parameters of the order
operation so that it is the superset of both operations. This will allow you to access the needed inputs to call order item
.
Then you will need to write a custom resolver that will return the desired data; this will most likely be a call to order
and another call to order item
and then combining the two and returning the final data.
You will not know if the user has requested items
. You will just have to make both calls. The only way you can avoid this is by adding an additional argument so that the user can signal whether he/she will select items
but this is ugly and not idiosyncratic. This is why I recommend seeing if you can make the links feature work.
Just to clarify, your second suggestion will not work. OtG only takes an OAS as input. We cannot take schemas as input so anything from Apollo will not work.
Hope this helps!
Hi @Alan-Cha
You have understood the situation correctly. Also, I've amended my original question slightly to consistently refer to order items
. Unfortunately this may make your answer slightly confusing to future readers - at least until they see this comment.
You're also absolutely correct, the link feature is what I need to use, not customResolvers
.
I've made some headway with this but I need to do some more experimenting.
As ever, thanks for your assistance.
@simax If you need any assistance, please by all means reach out. We are very happy to help! At the same time, we would love to get any feedback on using the link feature.
The link feature is arguably our most important feature (it is the second feature we list under characteristics) and we really want people to try it out so if you see anything that's not intuitive, then that means we still have plenty to improve on documentation wise.
In any case, best of luck!
Hi,
As I said previously I've made some progress with the link feature but I'm still struggling to make things work as I need them. It's entirely possibly that my approach isn't correct - again your guidance would be appreciated.
I've attached the schema I'm using that's generated by OtG. I've then edited the schema and added a link object
at line 122. just to investigate how link objects work.
Doing that "sort of" works. It allows me to issue a query like so:
{
orderV2(id: 91) {
items {
data {
id
}
}
data {
id
guid
totalCost
orderNumber
orderDate
}
}
}
This works well as it only hits the endpoint in our API to access order items
when I mention items
in the query. All good.
Obviously there is a problem with the "shape" of the query. I really want to issue a query like this:
{
orderV2(id: 91) {
data {
id
guid
totalCost
orderNumber
orderDate
items {
data {
id
}
}
}
}
}
But I don't know how to add the link object to achieve that.
The other query I'd like to do is similar but I want to get a list of order
objects, each with their associated order items
. I see from the docs that such a query should be possible but the way things stand I'm unable to achieve that.
I'm not sure if you're able to load the schema I've attached and use it in something like GraphiQL
but if you can you will see that the documentation for it shows orderV2
endpoint defined like this:
orderV2(id: Float!): Order
with Order
defined like so:
data: OrderInfo
errors: JSON
item: OrderItems
Links: [LinkInfo]
meta: JSON
validationResult: [ValidationErrorInfo]
OrderInfo
then contains all the data about the order. The id, orderNumber, orderDate etc...
The ordersV2
(List of orders) is defined like this:
ordersV2(limit: Int page:Int): Orders
Where Orders
is defined like this:
data: [OrderInfo]
errors: JSON
item: OrderItems
Links: [LinkInfo]
meta: JSON
validationResult: [ValidationErrorInfo]
As you can see, the OrderInfo
objects are nested inside other objects and it looks like that is problematic.
So my questions are:
Am I approaching this correctly?
Is this an idiomatic way to produce a GraphQL interface to my API?
What else do I need to do regarding link objects
to achieve my goals?
Difficult questions I know but hopefully you can help.
Hi! Sorry for the late response! I just got back from a conference so I needed a day to get back into the flow of things 😅
These are very good questions!
First of all, you are employing link object correctly, however, it seems that the way that we employ link objects may not perfectly suit your use case.
Both Order
and Orders
offer additional layers of abstraction and metadata which OtG is not aware of and built to consider.
We expect simpler responses, where orderV2
returns OrderInfo
and ordersV2
returns [OrderInfo]
. If this were the case, the items
field (constructed from the link) would be added to OrderInfo
and would be accessible on orderV2
and any element of ordersV2
.
In the context of the documentation, orderV2
would map to user
, ordersV2
would map to friends
, and items
would map to employer
.
Additionally, the link definition would only need to be defined on orderV2
and because of how we collapse links and reuse object type definitions, it would be accessible to elements one ordersV2
.
However, because there are these additional layers of abstraction, we do not handle this use case properly. I am guessing items
only shows up on Order
but not on Orders
. It is not in the desired place but at least it functions correctly.
Unfortunately, we have not considered API designs like these before and so OtG currently does not have the capacity to create the GraphQL interface you would like.
Moving forward, we have a few questions to ask. Should we support this kind of use case? And if so, how?
Regarding the first question, this has always been a grey area, the trade-offs in auto-generation tools, balancing flexibility with conciseness, adding new (perhaps niche) features, etc. However, I think this use case is probably quite common and because we really want to push link objects in OtG, I think this will be a powerful and useful feature (the ability to control where link fields are added) to have.
As to how, I'm not really sure... Perhaps the user needs to define a JSON path to where the field should be added... Maybe it should work like customResolvers
where we allow the user to select a specific operation... ideas?
Thanks as always for your thoughts. Hope you enjoyed the conference.
You're right, it's the extra layers of abstraction that are causing the issue, if it wasn't for that, things would work as described in the docs.
It is difficult to know how to move forward. We'll have to think about it a bit more and come back to you with any suggestions we feel might be useful.
I do not think we necessarily need to close this issue. I think we can move ahead with this proposal. We just need to figure out the implementation details, which is currently an open discussion.
Would love to get input from @ErikWittern and @wtrocki! 😄
Hi @Alan-Cha, you're right, we should keep this issue open.
I talked to @ErikWittern and he told me this proposal might be stretching it a bit far. I can see where he is coming from. At some point with these auto-generation tools, you have to draw the line somewhere. Even so, I am still on the fence about it. I think there is potential in the idea, the hardest part is really how to configure the option.
Hoping other people would also like to join the conversation.
Right, to reaffirm what Alan stated, I think configuring where in a returned object the field holding linked data resides is a) difficult to configure, and b) reaches, in my opinion, a degree of configurability where writing a custom GraphQL wrapper may become more sensible than using OtG.
However, at the same time, we recognise that a lot of recent issues resolve around specific configuration options users desire, and many have to do with resolvers. Allowing custom resolvers was a first step in providing more flexibility, but maybe there is even more we can do by allowing a more fine-granular use of OtG. Some initial discussions were had with @wtrocki recently.