-
Notifications
You must be signed in to change notification settings - Fork 493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Struct Field Resolver instead of Method #194
Conversation
Use Struct Field Resolver instead of Method
@tonyghita, please take a look at this when you get a chance, thanks. |
Finally ! Thanks a lot @salmana1 this looks promising. |
Also updated `social.go` example
Handle parsing of `time.Time` type struct fields
Fixed bug to handle Union type
please merge request!!! |
@tonyghita any ETA on when you would have some time to review (and approve) this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an awesome PR! Thank you very much 🎉
I'd would vote 👍 for this change!
However, it would be better if you submit unrelated changes in separate PRs. Please, submit a separate PRs for:
- resolving IDs as strings (I'd vote 👎 )
<field_name>Resolver
to be used if available (I'd vote 👎 )
Sending multiple changes in a single PR makes it much harder to review. Also, if someone doesn't agree with all of the changes, it would take a lot of time to get your PR merged.
Thanks again! 🥇 🏆
|
||
To run this server | ||
|
||
`go ./example/field-resolvers/server/server.go` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean go run
?
|
||
func init() { | ||
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} | ||
schema = graphql.MustParseSchema(social.Schema, &social.Resolver{}, opts...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(minor) Is there any reason for these to be in the init()
func? They could easily be just live in a var(
block or live in main()
.
type Query { | ||
admin(id: ID!, role: Role = ADMIN): Admin! | ||
user(id: ID!): User! | ||
search(text: String!): [SearchResult]! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix indentation, please.
case *string: | ||
implementsType = (t.Name == "String") | ||
// allow ID of type string | ||
implementsType = t.Name == "String" || t.Name == "ID" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unrelated change. Please, send a separate PR for it.
} | ||
|
||
type user struct { | ||
Id string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, renameId
-> ID
.
func (b *execBuilder) makeObjectExec(typeName string, fields schema.FieldList, possibleTypes []*schema.Object, nonNull bool, resolverType reflect.Type) (*Object, error) { | ||
func (b *execBuilder) makeObjectExec(typeName string, fields schema.FieldList, possibleTypes []*schema.Object, | ||
nonNull bool, resolverType reflect.Type) (*Object, error) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, remove empty line.
// 1.2) Or field has arguments | ||
// 1.3) Or it is configured to use method | ||
// 1.4) Or it is an interface | ||
// 2) Otherwise use resolver type's field |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, reformat the comment as:
// 1. Use method resolver when:
// a. __Type and __Schema requests; or
// b. The requested field has arguments; or
// c. The schema is not configured to use field resolvers; or
// d. It is an interface
// 2. Otherwise, use a field resolver
// 1.4) Or it is an interface | ||
// 2) Otherwise use resolver type's field | ||
if isResolverSchemaOrType(rt) == true || len(f.Args) > 0 || | ||
b.schema.UseFieldResolvers == false || rt.Kind() == reflect.Interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UseFieldResolver
is already a boolean, right? Can't you just use !b.schema.UseFieldResolvers
? Or you want it to be more verbose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imho, !
can be overlooked sometime so more verbose make it clear that it's a check for false
. I can change this to !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer using !
because it'll be consistent with the rest of the code in this library.
} | ||
if err := b.assignExec(&a.TypeExec, impl, resolverType.Method(methodIndex).Type.Out(0)); err != nil { | ||
return nil, err | ||
if b.schema.UseFieldResolvers == false || resolverType.Kind() != reflect.Interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again <bool> == false
vs !<bool
? Do we want the more verbose version? IMHO, it's better to use !
...
func findMethod(t reflect.Type, name string) int { | ||
resName := name + "Resolver" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is completely unrelated change (i.e. finding method with name <field>Resolver
). Please, send this in a separate PR. I'd personally, vote 👎 for this specific change, since adds unnecessary complexity to the library. Less is more in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not an unrelated change. As mentioned in the PR notes, when a struct implements an interface, then struct's resolver methods are used for each interface field. Hence, when a struct has a field Email
and interface has the same field, then there will be a conflict between struct field and resolver method. So a Resolver
suffix can be used to resolve that conflict. If you can suggest something else to achieve the same goal then I will be happy to implement that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a workaround you can just use different struct fields. If you are implementing an interface, it's just the methods that will be used, right? This would mean that you can use differnt field names.
@tonyghita, please, review this change as it is opt-in and would save us from writing a lot of boilerplate code. 🎉 🆒 😎 👍 ❤️ I'm curious to see the benchmark comparison between the field and method resolvers. 📈 📉 📊 |
@neelance, is this getting a step closer to what you suggested in #88 (comment) or is it a move in a different direction? |
@pavelnikolov thanks for reviewing the code and I will fix most of them. I totally understand what you are saying about multiple PRs. Please note that, this PR is coming from downsteam
I think this is useful change and it's working well for my company. We don't have to wrap or unwrap
This is relevant to this PR and I have replied to your review comment |
I'm still not 100% convinced that this is necessary. You can definitely change the field names in the struct or use a different struct and still implement an interface. I think that it should live in a separate PR. |
I'll create a test schema and will run benchmarks comparing the |
Can this get some attention? This is pretty handy for reducing the amount of boilerplate needed. |
+1, This feature would be very practical. |
In large applications this would take away a lot of boilerplate code. Is there anything that still needs to happen for this to be merged? |
@tonyghita is this in your roadmap? Any feedback about this PR? |
Thanks for working on that @salmana1 ! 🙇🏼♂️ |
push |
I'd be happy to consider the struct field resolvers once you split this PR. |
+1!! This is a must-have for reducing drastically the codebase. |
@salmana1 are you willing to split this PR? I'd be happy to review/merge the field resolvers. |
@salmana1 Let me know if you need any help. Also, when passing |
@bazaglia Thanks for the feedback. Any feedback is highly appreciated! Can you please add some samples / unit-tests? |
@pavelnikolov I don't have enough Go experience to write unit tests. I just tested the functional part of the PR, and it worked really well, resulting in much cleaner code. My only consideration is the way it's currently implemented, it looks like the developer has to choose OR to use struct fields as resolver OR to use the methods as the resolver (which is the standard in current graphql-go). My suggestion is to go for a hybrid approach: resolve data from the struct value, which is the default way, but also allows to override the struct value, using a method to do it. This approach is also really close to graphql-js implementation: there's a default resolver, which is the value of the field from struct, but also allows implementing a function, to set the resolver. I really wish I had experience with Go to help with this functionality, I think this PR is so great as it makes the code much more legible & readable. |
Sorry for not replying sooner. I will try to update this PR soon. I don't have access to the original forked branch; so, I will probably have to close this PR and recreate it. On a separate note, I would recommend to take a look at gqlgen library. At my last job, we switched to that library after we realized this library was not actively developed and PRs weren't getting merged. |
Thanks for your contribution, it is highly appreciated! P.S. It might take a wile until all existing issues and PRs are cleaned up. Thank you all for your patience. The current priority is subscriptions... |
I would really like to see this PR getting merged! |
@tobstarr I am thinking of an implementation that would keep the current method implementation as default but would allow the users of the library to override the resolving implementation as they see fit. I'll post an update soon. Chances are that this PR will not be merged in its current form. |
@pavelnikolov nice, that's my upvote also, just like I commented before. |
I will be pushing out a cleaned up PR tomorrow and will close this PR. Your suggestion is really good and I am happy to discuss it with you guys in detail. |
Closing this PR - please see the new PR #282 |
This fixes #28. Essentially, it reduces lot of boilerplate code by making use of struct field as resolvers instead of creating methods for each struct field.
Now, methods are required only when
first: Int, last: Int
etc.union
typeBy using struct fields as resolvers, one could also benefit from DRY codebase by not having a pair of a struct which look the same (one for GraphQL and one for DB)
Does this break existing API?
No. This change is completely transparent and you can continue using your existing codebase.
Can I still continue using methods as resolvers?
Yes, by default, it uses method resolvers.
How can I use struct fields as resolvers?
When invoking
graphql.ParseSchema()
orgraphql.MustParseSchema()
to parse the schema, you pass ingraphql.UseFieldResolvers()
as an argument. For an example, take a look at./example/social/server/server.go
Additional Notes
./example/social/social.go
&./example/social/server/server.go