gqlgen icon indicating copy to clipboard operation
gqlgen copied to clipboard

usage of field resolver when it is used in multiple resolvers

Open vishnuitta opened this issue 3 years ago • 5 comments

What happened?

Followed this link https://github.com/99designs/gqlgen#using-explicit-resolvers to have field based resolvers so that they are fetched only when they are asked for.

  Todo:
    fields:
      user:
        resolver: true

Below is the model, and the two queries which returns this model.

type Todo {
  id(version1: String!): ID! @directive1
  text: String!
  done: Boolean! @deprecated(reason: "some reason")
  user: User!
}

type Query {
  todos(version: String!): [Todo!]!
  todos_ex(version: String!): [Todo!]!
}

On performing go run github.com/99designs/gqlgen generate, below is the resolver got added.

func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) {

But, I have a requirement to perform different operations in User todoResolver based on the type of query resolver which it is called. Same model Todo might be returned in mutations as well.

It would be helpful if there are steps that I can do to achieve this.

What did you expect?

I expected a seperate field resolver for each query/mutation/subscription resolver it is returned from (or) any other way to achieve this.

Minimal graphql.schema and models to reproduce

versions

  • gqlgen version?
  • go version? go1.14.2
  • dep or go modules?

vishnuitta avatar Jan 27 '21 13:01 vishnuitta

some insights on this issue would be really helpful.. otherwise, using 'explicit resolvers' is a pain with lot of duplicate code. cc: @frederikhors pardon me if I tagged you wrongly

vishnuitta avatar Jan 29 '21 05:01 vishnuitta

What about wrapping your duplicated code in own methods?

razorness avatar Feb 01 '21 12:02 razorness

@razorness duplication is not just with methods, but, with structures also. Below is one way I'm thinking to have a 'resolver' based on type of query resolver with lot of duplicate structs and also duplicate code:

  Todo:
    fields:
      user:
        resolver: true
  TodoEx:
    fields:
      user:
        resolver: true

Below is the models, and the two queries.

type Todo {
  id(version1: String!): ID! @directive1
  text: String!
  done: Boolean! @deprecated(reason: "some reason")
  user: User!
}
type TodoEx {
  id(version1: String!): ID! @directive1
  text: String!
  done: Boolean! @deprecated(reason: "some reason")
  user: User!
}

type Query {
  todos(version: String!): [Todo!]!
  todos_ex(version: String!): [TodoEx!]!
}

On performing go run github.com/99designs/gqlgen generate, below resolvers should get added.

func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) {
func (r *todoexResolver) User(ctx context.Context, obj *model.TodoEx) (*model.User, error) {

Above approach ends up in maintaining multiple structs for same type.

I would like to know other possibilities for this issue.

vishnuitta avatar Feb 01 '21 13:02 vishnuitta

func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) {
    return r.loadUserById(obj.UserID)
}
func (r *todoexResolver) User(ctx context.Context, obj *model.TodoEx) (*model.User, error) {
    return r.loadUserById(obj.UserID)
}

Why is this no solution? I have ton of properties pointing so same object like this. I implemented the dataloader pattern and have many resolver methods which only one line of code.

razorness avatar Feb 01 '21 14:02 razorness

i have another solution to make each resolver have independent file based on Mutation/Query, my final folder structure looks like this

├── app
│   ├── repository
│   │   ├── todo.repository.go
│   │   └── user.repository.go
│   └── resolver
│       ├── resolver.go
│       ├── schema.resolver.go
│       ├── todo.resolver.go
│       └── user.resolver.go
├── cmd
│   ├── migrate.go
│   └── serve.go
├── database
│   └── conn.go
├── go.mod
├── go.sum
├── gqlgen.yml
├── graph
│   ├── generated
│   │   └── generated.go
│   ├── model
│   │   └── models_gen.go
│   ├── resolver.go
│   ├── schema.graphqls
│   └── schema.resolvers.go
├── graph-gen.sh
├── main.go
├── makefile
├── migrations
│   ├── 1_role.up.sql
│   └── 2_user.up.sql
└── readme.md

to achive that you need create 1 folder to store all separated resolver, and copy resolver.go, schema.resolver.go to your new resolver folder or just modify config gqlgen.yml

# Where should the resolver implementations go?
resolver:
  layout: follow-schema
  dir: graph  # in my case changed to app/resolver
  package: graph #  changed to resolver

and re-generate the schema.

next change executable schema

// server.go before
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

// server.go after
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolver.Resolver{}}))

then you can focus code on each resolver without worry bloated resolver files.

fachryansyah avatar Jul 16 '21 19:07 fachryansyah