Skip to content
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 #1

Merged
merged 2 commits into from
Apr 11, 2018
Merged

Conversation

0xSalman
Copy link

@0xSalman 0xSalman commented Apr 2, 2018

This fixes graph-gophers#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

  1. a struct implements a GraphQL interface. In this case, you create methods for fields which overlap
  2. a struct exposes a GraphQL field that accepts additional arguments i.e., first: Int, last: Int etc.
  3. there is GraphQL union type

By 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() or graphql.MustParseSchema() to parse the schema, you pass in graphql.UseFieldResolvers() as an argument. For an example, take a look at ./example/social/server/server.go

Additional Notes

Copy link

@edlau edlau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visually inspected two passes, looks good! PR notes is good too!

@0xSalman 0xSalman force-pushed the impl-field-resolver branch from 6bdf423 to bded7d4 Compare April 3, 2018 13:42
@Niennienzz
Copy link

Digged a bit into the library yesterday for learning. I tried the method way and the struct field way, and both servers worked nicely. I just have a very subtle suggestion in the example code that:

errors.New(fmt.Sprintf( ... ))

could be replaced by a shortcut:

fmt.Errorf( ... )

at lines 160 and 168 of example/social/social.go.

We will probably use Salman's error handling library a lot, so anyways. 😃

@0xSalman
Copy link
Author

0xSalman commented Apr 3, 2018

@Niennienzz good suggestion - I will make the change

@0xSalman 0xSalman closed this Apr 3, 2018
@0xSalman 0xSalman reopened this Apr 3, 2018
@dabfleming
Copy link

Still reviewing but so far I have one suggestion. Instead of adding config.Config, why not build on the existing functional options using SchemaOpt? This would have the advantage of not requiring any breaking changes or the addition of the config package.

For example, expand graphql.Schema like so

// Schema represents a GraphQL schema with an optional resolver.
type Schema struct {
	schema *schema.Schema
	res    *resolvable.Schema

	maxParallelism    int
	tracer            trace.Tracer
	logger            log.Logger
	useFieldResolvers bool
}

and add

// UseFieldResovlers opts in to using struct field resolvers
func UseFieldResolvers() SchemaOpt {
	return func(s *Schema) {
		s.useFieldResolvers = true
	}
}

It's simpler, non-breaking, and the zero value, useFieldResolvers == false represents the current behaviour. You could then use field resolvers by:
ParseSchema(schema, resolver, UseFieldResolvers()) similar to passing options like WithInsecure() to grpc.

@0xSalman
Copy link
Author

0xSalman commented Apr 3, 2018

@dabfleming

That's a very good suggestion. I initially explored that option. I don't remember now why I didn't continue with it. I would, however, keep Config and make it more generic because I am sure we will make use of it when we have to configure things like query's maxDepth, cost etc.

I am thinking something like this:

package graphql
...

func AddConfiguration(conf *config.Config) SchemaOpt {
    return func(s *Schema) {
        s.Config = conf
    }
}

and then

ParseSchema(schema, resolver, graphql.AddConfiguration(&config.Config{}))

@0xSalman
Copy link
Author

0xSalman commented Apr 4, 2018

David,

I went ahead and refactored the code as per your suggestion. It seems to be a better solution than my proposal of using a config struct.

@0xSalman 0xSalman force-pushed the impl-field-resolver branch from b74a087 to b463cbc Compare April 4, 2018 13:05
graphql_test.go Outdated
@@ -61,7 +61,7 @@ func (r *timeResolver) AddHour(args struct{ Time graphql.Time }) graphql.Time {
return graphql.Time{Time: args.Time.Add(time.Hour)}
}

var starwarsSchema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{})
var starwarsSchema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}, nil)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@salmana1 3rd param still needs to be removed here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// this approach avoids updating signature of many functions
var useFieldResolvers bool

func ApplyResolver(s *schema.Schema, resolver interface{}, useFieldRes bool) (*Schema, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding global state, how about adding a field to schema.Schema? There's a pointer to it everywhere useFieldResolvers is read: both in this func and as a member of resolvable.execBuilder (other usages are in methods on execBuilder).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@0xSalman 0xSalman force-pushed the impl-field-resolver branch from e945f80 to b0effb8 Compare April 5, 2018 16:11
Copy link

@Niennienzz Niennienzz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through it again. I really like the way you interactively worked on this one.

@0xSalman 0xSalman force-pushed the impl-field-resolver branch from b0effb8 to de4e1e1 Compare April 11, 2018 16:34
@0xSalman 0xSalman merged commit 2fcff20 into master Apr 11, 2018
@0xSalman 0xSalman deleted the impl-field-resolver branch April 11, 2018 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants