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

Update documentation with advice on how to achieve schema namespacing #863

Closed
can-axelspringer opened this issue Dec 6, 2023 · 5 comments
Assignees
Labels
type: documentation A documentation task
Milestone

Comments

@can-axelspringer
Copy link

can-axelspringer commented Dec 6, 2023

Hello,

I hope this is the right place to ask this question. I could not find anything on the internet that could provide me with an answer.

Is it possible to define schemas like following:

type UserQueries {
    get(id: String! @NotEmpty): String
}

type UserMutations {
    delete(id: String! @NotEmpty): String
}

extend type Mutation {
    users: UserMutations
}

extend type Query {
    users: UserQueries
}

When I attempt to do it like that. I can't make annotated controller model work.

@Controller
@SchemaMapping(field = "users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @SchemaMapping(typeName = "UsersMutations", field = "delete")
    String delete(@Argument String id) {
        userService.delete(id);
        return id;
    }

}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Dec 6, 2023
@wadajo
Copy link

wadajo commented Dec 14, 2023

As far as I understand, in Spring for GraphQL you should define your schema like this:

type Mutation {
    deleteUser(id: String! @NotEmpty): String
}

type Query {
    getUser(id: String! @NotEmpty): String
}

And then, in your Controller:

@QueryMapping
String getUser(@Argument id){
        // business logic
    }

@MutationMapping
String deleteUser(@Argument id){
        // business logic
    }

@Jamel-jun
Copy link

Stacked into one file, it is very difficult to maintain. Is it possible to support sub-modules Query and Mutation?

@bclozel bclozel self-assigned this Mar 20, 2024
@bclozel
Copy link
Member

bclozel commented Mar 20, 2024

Sorry for the delayed response. I had a look into this and I'll share my findings here.

First @SchemaMapping at the type level is already supported for the typeName field as described in the annotation support section of the reference documentation. This is implemented by the AnnotatedControllerConfigurer.

I think that the suggested approach with @SchemaMapping(field = "users") doesn't really make sense here, since our controller method provides a data fetcher for the UserMutations type and its delete field. It is declared as it should on the controller method directly with @SchemaMapping(typeName = "UsersMutations", field = "delete").

Here's, what's missing is the actual UserMutations type and how it relates to our application. I managed to implement this in a sample application with the following:

type Query {
    music: MusicQueries
    users: UserQueries
}

type MusicQueries {
    album(id: ID!): Album
    searchForArtist(name: String!): [Artist]
}

type Album {
    id: ID!
    title: String!
}

type Artist {
    id: ID!
    name: String!
}

type UserQueries {
    user(login: String): User
}

type User {
    id: ID!
    login: String!
}
package io.spring.sample.graphqlnamespace;

import java.util.List;

import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;

@Controller
@SchemaMapping(typeName="MusicQueries")
public class MusicController {

    @QueryMapping
    public MusicQueries music() {
        return new MusicQueries();
    }

    @SchemaMapping
    public Album album(@Argument String id) {
        return new Album(id, "Spring GraphQL");
    }

    @SchemaMapping
    public List<Artist> searchForArtist(@Argument String name) {
        return List.of(new Artist("100", "the Spring team"));
    }

    public record MusicQueries() {

    }

    public record Album(String id, String title) {
    }

    public record Artist(String id, String name) {
    }

}
  1. declaring @SchemaMapping(typeName="MusicQueries") on the controller avoids to repeat this typeName declaration on all endpoints.
  2. the @QueryMapping declaration provides an "empty" MusicQueries object, but could also return an empty map. Here, we could choose to move this to a separate controller if we wanted to (to regroup all root namespaces).

I don't think additional support is needed in Spring for GraphQL right now. We could turn this into a documentation issue and mention this pattern in the reference docs. @can-axelspringer would this work for you?

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label Mar 20, 2024
@rstoyanchev
Copy link
Contributor

The UserQueries and UserMutations are just wrappers, and it doesn't mater what type is returned, even an empty map should do, since all of their child fields are mapped. You could do that from a controller as Brian showed above, or you could also register them all from a single place like so:

@Bean
public GraphQlSourceBuilderCustomizer customizer() {

	List<String> queryWrappers = List.of("users", ... );
	List<String> mutationWrappers = List.of("users", ... );

	return sourceBuilder -> sourceBuilder.configureRuntimeWiring(wiringBuilder -> {

		queryWrappers.forEach(field -> wiringBuilder.type("Query",
				builder -> builder.dataFetcher(field, env -> Collections.emptyMap())));

		mutationWrappers.forEach(field -> wiringBuilder.type("Mutation",
				builder -> builder.dataFetcher(field, env -> Collections.emptyMap())));
	});
}

There isn't much we can do to make this easier, and it seems pretty straight forward in any case.

@can-axelspringer
Copy link
Author

Thank you very much for the help and support.

I was able to reproduce proper namespacing setup using given examples.

@bclozel and @rstoyanchev
I checked the documentation before creating this issue but was not able to realize name-spacing setup in our project.
It might have been due to my lack of understanding of the documentation or documentation does not delve deep into annotated controller model enough, but it could be great if we can have a subsection in the documentation for name-spacing and separation of concerns.(?)

Thank you all again.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 21, 2024
@bclozel bclozel added type: documentation A documentation task and removed status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged labels Mar 21, 2024
@bclozel bclozel added this to the 1.3 Backlog milestone Mar 21, 2024
@rstoyanchev rstoyanchev changed the title Namespacing Update documentation with some advice on schema namespacing Mar 21, 2024
@rstoyanchev rstoyanchev changed the title Update documentation with some advice on schema namespacing Update documentation with advice on schema namespacing Mar 21, 2024
@rstoyanchev rstoyanchev changed the title Update documentation with advice on schema namespacing Update documentation with advice on how to achieve schema namespacing Mar 21, 2024
@bclozel bclozel modified the milestones: 1.3 Backlog, 1.2.7 Apr 12, 2024
bclozel added a commit that referenced this issue Apr 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

6 participants