Skip to content

An example of the nested mutation #64

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

Closed
limenutt opened this issue Apr 19, 2018 · 7 comments
Closed

An example of the nested mutation #64

limenutt opened this issue Apr 19, 2018 · 7 comments
Labels
Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved

Comments

@limenutt
Copy link

Hi!
First of all, great library, thanks a lot!
I'm currently building a graphql API and want to make it as generic as possible.
I have many objects and want to have same generic mutations for each object type (create, update and delete). At the moment, I have implemented a resolver for each of my object types, each with it's methods so I can update my objects like this:

mutation {
   addBook(input: $bookInput) {
     id
  }
}
mutation {
   addUser(input: $userInput) {
     id
  }
}
mutation {
   addProject(input: $projectInput) {
     id
  }
}

Is there a way to instead nest the mutation under the object name, like so

mutation {
   book 
     add(input: $bookInput) {
       id
    }
}
mutation {
   user 
     add(input: $userInput) {
       id
    }
}
mutation {
   project 
     add(input: $projectInput) {
       id
    }
}

In this case I could implement the basic mutations (add, update and delete) in the base resolver class and just inherit from it in object-specific resolvers, maybe use some decorators to override the input types.

@MichalLytek
Copy link
Owner

Nested mutations sound nice, I haven't thought about it but it seems possible to do.

However, about the resolver inheritance part:

I could implement the basic mutations (add, update and delete) in the base resolver class and just inherit from it in object-specific resolvers

We've already discussed it:
#47
And it's just better to have less magic and inheritance problem. Decorator duplication is not an issue, it's like a documentation for your API. You can move your common CRUD logic to generic service and inject the specific extended services per entity type.

@MichalLytek MichalLytek added Enhancement 🆕 New feature or request Discussion 💬 Brainstorm about the idea labels Apr 20, 2018
@MichalLytek
Copy link
Owner

MichalLytek commented Apr 20, 2018

@limenutt
I've done some research and this pattern doesn't match semantically to the mutation constraints.

In GraphQL spec, project, book or user would have to be GraphQLOutputType, in this case ObjectType. So add, delete, edit would have to be object's field which resolvers just takes arguments.

So the problem is that clients could do parallel mutation by e.g.:

mutation {
  project {
    add(input: { name: "test2" }) {
      id
    }
    edit(input: { id: 2, name: "test2 edit" }) {
      id
    }
    delete(id: 2) {
      id
    }
  }
}

And as they're resolvers are attached to ObjectType, they behave like the query, so they are executed in parallel.

But by design, mutation are changing the app/server state so they have to be reliable.
When client sends multiple mutation, they are executed sequentially, which wouldn't be true in the nested "mutation" example.

So I think that this approach to designing schema doesn't give any improvements to the clients, just have negative impacts. As you will have generic service for common business logic, it's easy to create separate one-line resolvers with mutations like addUser, editBook, etc.

If this answer exhaust the topic, please close the issue 😉

@limenutt
Copy link
Author

Thank you, I appreciate your prompt thorough response!
Yes, I think your argument makes sense.

@MichalLytek MichalLytek added Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved and removed Discussion 💬 Brainstorm about the idea Enhancement 🆕 New feature or request labels Nov 12, 2020
@chrisdostert
Copy link

chrisdostert commented Jun 12, 2021

Just adding my opinion this would be awesome to have. There are examples of mainstream API's taking this tact successfully. In my experience, having resolvers at root level results in an anemic domain model and should be avoided if possible.

@MichalLytek
Copy link
Owner

having resolvers at root level results in an anemic domain model

I have no idea why nesting might help you with domain model.

If you want to have:

query {
  user {
    create(...) {
      # ...
    }
  }
}

you can just join the two words into a one camelCase'd:

mutation {
  userCreate(...) {
    # ...
  }
}

TypeGraphQL let define such schema in a decoupled fashion. Instead of adding @FieldResolver decorator just use @Mutation and the rest of the code stays the same.

In my experience, having resolvers at root level results in an anemic domain model and should be avoided if possible.

GraphQL is designed around root level queries and mutations and you need to live with this.
Trying to introduce REST patterns of /resources/subresources/action makes no sense.
There's no need to fight against it.

@mwalkerr
Copy link

It wasn't immediately clear to me how to make this work, so here's some example code to help anyone else trying to do this:

@ObjectType()
class BookQuery {
  @Field(() => Book)
  async add(@Arg("id") id: string) {
    return ...
  }
}
@ObjectType()
class BookMutation {
  @Field(() => Boolean)
  async add(@Arg('book') book: Book) {
    return ...
  }
}


@Resolver()
export class BookResolver {
  @Query(() => BookQuery, { name: 'book' })
  bookQuery() {
    return {};
  }

  @Mutation(() => BookMutation, { name: 'book' })
  bookMutation() {
    return {};
  }
}

@MoSattler
Copy link

It would be cool to be able to nest mutations to get work done in fewer requests. Imagine you want to add an Author and one of his Books in one request:

createAuthor(input: $authorInput) {
  name
  createBook(input: $bookInput) {
    title
  }
}

That'd be neat. But it is not currently part of the Graphql spec, see the discussion here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved
Projects
None yet
Development

No branches or pull requests

5 participants