-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[Proposal] Always use pointers in resolver return types #375
Comments
I think defaulting to a pointer makes sense. The current behavior has definitely been an annoyance at times. What about types that are normally passed by value (except when nullable), like |
And |
Having worked with gqlgen with a small to medium sized schema for about 6 months now, I can definitely say that I would prefer this change, largely for the benefits listed above. |
@vektah real drawback of always-pointers approach - they can be nil for non-null schema. This makes unnecessary null-check and makes it less clear for developer if there can return |
@azavorotnii we're talking about the return value from resolvers though. I think I would still expect gqlgen to generate resolver signatures that take non-pointer arguments for non-nullable GraphQL arguments. That way only gqlgen needs to do the null checking. @vektah was that what you were imagining with this change? |
@mathewbyrne I understand that this is only about return values. But assuming schema:
We will get resolver with nullable return value which can be defined like this by developer:
Which will end up as
|
To me this seems fine, and exactly within the domain gqlgen is responsible for. We're generating the code so it's no problem to add a check like this into the template. I believe it's also easier for the resolver developer to not have to worry about this, as well as having the additional benefits outlined earlier in this thread. |
It already is responsible, Any NonNull field that has an error can already be returned as null, and the spec is pretty clear on how it should be handled.
Yeah, this was my initial thinking too, but in practice I've grown to hate it. It gets in the way more than it helps.
This is actually totally valid, and would already produce an error like this if it were marked as NonNull and the user wasn't found.
However, be very careful about null bubbling behaviour, this is very rarely what you want for find methods like this! By forcing |
It seems to me that this is still not fully implemented. When there is a list of objects then the object is still not a pointer (0.8.3).
gives:
I expect it to be:
|
Sorry to reopen here... but I fundamentally disagree with the original statement. Most database tools I've used (sqlx for example) don't return pointers. Making everything a pointer means you have to do nil checking (mentioned above), but it also means that you can't compare objects without de-referencing them. I am of the belief that pointers should only be used where you actually need to mutate the original state, in order to share it between different actors (not to be confused with threads/routines). I would really appreciate some feedback here, this is something that should be changed. Dave Cheney mentions some points in the following articles that kind of help back this opinion.
Are we trying to return pointers or values? Also, @PatrLind those slices are technically pointers already, and thus can be nil: https://play.golang.org/p/IpyzWkUVRqJ |
@rucuriousyet, I have no preference over either pointers or values (copies), but the reason I commented was because the interface was inconsistent. Some things were returned as pointers and some as values regardless of nullable or not. |
Agreed! I think versioning the project might be a good idea if changes are going to be implemented like this. |
From the proposal in the first comment, I can understand why the change was introduced for single things, e.g. *User instead of User (and I think this is a good change). However, I don't quite understand why it was introduced for slices as well, as it was just added in the PR but I could not find any information or reasoning for this specific change. This makes gqlgen incompatible with Prisma, because while Prisma returns pointers for single values it returns a slice of structs for multiple values. Was the slice-of-pointers change just introduced for consistency, so [User], [User!] and [User!]! will generate the same code? Would it really make sense for a tool like Prisma to return a slice of pointers? If yes, why? It would be great if someone could clarify this. |
@vektah, @mathewbyrne or @alexbyk; it would be great if one of you could clarify my questions above some time :) |
Same thing here: |
@vektah, @mathewbyrne and @alexbyk; Why is none of you answering to @steebchen? |
I have now managed to update my code in a rather bigger project for some other features in the latest release and I can only say that this update makes everything hideous, especially with Prisma. There is literally no single use case where it makes sense to return a slice of pointers, and it makes every resolver which returns an array extremely cumbersome to work with. Often, you end up doing this: users, err := r.Prisma.Users(...)
nodes := []*prisma.User{}
for _, item := range users {
clone := item // a clone is needed, otherwise the pointer to item is used
nodes = append(nodes, &clone)
}
return nodes Also, if you work with helper methods which return an array, you either have to implement the method to return a slice of pointers, which is also very error-prone, or (again) convert it always manually by using the method above. Of course, you can also create conversion methods for all types you are converting from, but please... I still don't understand why this change was made in the first place, and I don't understand why no one can answer me this. I assume because there is no answer, but silence doesn't help anyone. Why did you make this change? Why are you not considering reverting this change or making this at least configurable? This change is not documented, not discussed at all (except a single comment which suggests the change), it seems to confuse everyone, it's a huge refactoring burden, it makes working with slices of structs cumbersome to work with, and basically kills compatibility with Prisma. |
@steebchen Only today my knowledge of Golang allows me to understand what you complained about a year ago in this issue. May I know how you solved? Are you still using gqlgen? Do you use anything else? Thanks in advance. |
The config option Introduced in #874 |
Currently the return value for a resolver is based on the schema Non-Nullability.
eg
for
query { user: User!}
we generatefunc (r *Query) User(ctx context.Context) (User, error)
for
query { user: User}
we generatefunc (r *Query) User(ctx context.Context) (*User, error)
Initially, I thought this would be useful as the compiler can validate the non-nullability, however it:
return User{}, fmt.Errorf("not found")
Proposal
Regardless of nullability always use a pointer. Gqlgen already knows how to check for nulls in fields (used when binding directly to model fields that have pointers).
This would need to go behind a config flag for now. We can opt in new users in init, and output a deprecation message for some release in the future to drop. Or maybe we just keep it. Its probably a pretty small change either way.
What are peoples thoughts on this?
related prisma/prisma#3288
The text was updated successfully, but these errors were encountered: