I'm new to GraphQL.
// using interface
interface User {
name: String
}
type UserDetail implements User {
name: String
email: String
}
// just type
type User {
name: String
}
type UserDetail {
name: String
email: String
}
There is duplicated field name in interface User and type UserDetail.
I thought UserDetail doesn't have to include name.
I couldn't find detailed information of interface.
Why do I use interface, implements instead of just type?
And I want to know differences of using interface and type, advantages of using interface.
Please comment any advices.
Related
I am trying to resolve an interface by ID with netflix-dgs and Apollo federation. But the DgsEntityFetcher does not seem to be registered for interfaces.
I also tried declaring entity resolvers for types B and C and still null result.
Here is a sample:
interface A #key(fields:"id"){
id: ID!
...
}
type B implements A {...}
type C implements A {...}
//This type can appear in multiple services/subgraphs
type SomeObject #key(fields:"id"){
id: ID!
aTypeField: A #provides(fields: "id")
}
I have an entity class User with 20 fields, some of them being confidential fields. I have a controller class, which has a method getUser to fetch all the user from DB and send the JSON respone. Below is the sample code for the same:
#GetMapping("/getUsers")
public UserDT getUsers( Model theModel) {
List<User> userList;
userList = userService.findAll();
return userList;
}
When I run the above code, it returns all the fields from User table/User Entity Class. Instead of sending all the fields, I would like to send selected fields say Field1 to Field5 only.
Ultimate goal is to have multiple views for the same Entity Class. For URL1 I would like to show only field1 to field5 of User table, But for URL2 I would like to show Field9 , Filed15, Field20.
Do I need to create multiple Entity Class for each URL? Please guide me with the best practice to be followed in such scenario.
Assuming you are using Spring Data JPA, use projections.
So create different projections for your different URLs write a method that returns the projection (or a dynamic one as in the documentation).
public interface NamesOnlyProjection {
String getFirstName();
String getLastName();
}
public interface UserinfoProjection {
String getUsername();
String getPassword();
String getDepartment();
}
Then in your repository do something like this
public interface PersonRepository extends JpaRepository<Person, Long> {
<T> List<T> findAll(Class<T> type);
}
Then you can do something like this in your controller/service
#RestController
public class PersonController {
private final PersonRepository persons;
#GetMapping("/people/names")
public List<NamesOnlyProjection> allNames() {
return persons.findAll(NamesOnlyProjection.class);
}
#GetMapping("/people/users")
public List<UserinfoProjection> allNames() {
return persons.findAll(UserinfoProjection.class);
}
}
I have two projection interface,
First, ProductMinimal.java:
public interface ProductMinimal {
long getId();
String getName();
Long getOwnerId();
String getOwnerName();
String getFeaturedImage();
}
and second ProductStatistic.java
public interface ProductStatistic extends ProductMinimal {
int getTotalVisit();
}
When i use this repository:
Page<ProductStatistic> findMostView(Pageable pageable);
I get result, but wrongly mapped:
[{
name=1, WRONG
id=Name, WRONG
ownerId=administrator, WRONG
ownerName=21_d20024e8-970f-4738-9cea-8dda22d0afdd.jpg, WRONG
featuredImage=1, WRONG
totalVisit=21
}]
it should be:
[{
name=Name,
id=1,
ownerId=1,
ownerName=administrator,
featuredImage=21_d20024e8-970f-4738-9cea-8dda22d0afdd.jpg,
totalVisit=21
}]
but when I put all method in ProductMinimal to ProductStatistic and remove parent interface in ProductStatistic, so ProductStatistic all its own attribute:
public interface ProductStatistic {
long getId();
String getName();
Long getOwnerId();
String getOwnerName();
String getFeaturedImage();
int getTotalVisit();
}
using new this ProductStatistic, now I get expected result.
[{
name=Name,
id=1,
ownerId=1,
ownerName=administrator,
featuredImage=21_d20024e8-970f-4738-9cea-8dda22d0afdd.jpg,
totalVisit=21
}]
I don't like using this way, rewrite all existing property.
What I expect is to be able extends property from ProductMinimal, so i don't need to rewrite code.
Why using interface-based projection that extends other interface projection give wrong result?
It seems that the names defined in the .graphqls file MUST match the field names in the POJO. Is there a way to maybe annotate the field so that they don't have to?
For example, I have something like this in the graphqls file
type Person {
personId: ID!
name: String!
}
Then in my Entity POJO I have like
#Id
#Column(name="PERSON_ID")
#JsonProperty("person_id")
private int personId;
#Column(name="NAME")
#JsonProperty("name")
private String name;
So, the intention is for the field name to be personId and the database to store it as a column called PERSON_ID and for it to get serialized as JSON and GraphQL as person_id
But graphql talks in the language of the schema. So it serializes it as personId which matches the schema field but is not the intention. I could change the schema to be person_id but then I need to change the field too... This isn't the end of the world but it's quite "un-javalike" to have fields named that way.
I am using this library:
compile group: 'com.graphql-java', name: 'graphql-spring-boot-starter', version: '5.0.2'
I have also seen the #GraphQLName annotation in the annotations library but I must be missing something because it doesn't do what I am expecting or maybe I am using it wrong.
Is there some way to get around this or should I just change the names?
GraphQL Java uses PropertyDataFetcher by default to resolve field values (see data fetching section in the docs). This data fetcher works out-of-the box when the data object returned by a top level field data fetcher contains child fields that match the data object property names.
However, you can define your own data fetcher for any field and use whatever rule you need.
So, if you want a schema that looks like this
type Person {
person_id: ID!
name: String!
}
and your entity like this:
class Person {
private int personId;
private String name;
// getters and setters
}
You can write a simple custom data fetcher for the field personId
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
// query root data fetchers wiring
.type(newTypeWiring("Person")
.dataFetcher("person_id", environment -> {
Person person = environment.getSource();
return person.getPersonId();
})
)
// maybe other wirings
.build();
}
Error:FieldUndefined: Field 'createUser' in type 'Query' is undefined # 'createUser'"
#Service
public class GraphQlService {
#Value("classpath:schema.graphql")
Resource resource;
private GraphQL graphQL;
#Autowired
UserDataFetcher userFetcher;
#Autowired
PostDataFetcher postFetcher;
#PostConstruct
public void loadSchema() throws IOException {
File schemaFile = resource.getFile();
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
graphQL = GraphQL.newGraphQL(schema).build();
}
private RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
.type("Query", typeWiring -> typeWiring
.dataFetcher("user", userFetcher)
.dataFetcher("post", postFetcher))
.type("Mutation", typeWiring -> typeWiring
.dataFetcher("createUser", userFetcher))
.build();
}
public GraphQL getGraphQL() {
return graphQL;
}
}
1. Cant I use common datafetcher/reslover for both Query and Mutation
as I have done below in a single class.It is not able to find
createUser?
#Component
public class UserDataFetcher implements DataFetcher<List<User>> {
#Autowired
UserRepository userRepository;
public User createUser(DataFetchingEnvironment environment) {
String username = environment.getArgument("username");
String location= environment.getArgument("location");
User[] follower = environment.getArgument("followers");
User[] following = environment.getArgument("following");
Post[] pos = environment.getArgument("posts");
User user = new User();
user.setUsername(username);
user.setFollowers(follower);
user.setFollowing(following);
user.setLocation(location);
user.setPosts(pos);
return userRepository.save(user);
}
#Override
public List<User> get(DataFetchingEnvironment environment) {
return userRepository.findAll();
}
}
//SDL below for schema
schema {
query: Query
mutation: Mutation
}
type User{
id:ID!
username:String!
followers:[User]
following:[User]
location:String!
posts:[Posts]
}
type Post{
id: ID
title: String!
author: User!
}
type Query{
user: [User]
post: [Post]
}
type Mutation{
createUser(username: String!, location: String!,followers: [User],following:[User],posts:[Post]):User
}
2. Is the schema correct because it would say User and Post are not mentioned as InputType. I tried InputType for User and Post but
couldnt get it working.How should correct schema for storing
followers and following look like ?
1) No, each DataFetcher implementation can do a single operation only - that's the get method the interface specifies. How would the framework know to call the createUser method?
2) No, the schema is not valid. You can not use User as an argument (input) type. You need to define a separate input type, e.g.
input UserInput {
username:String!
followers:[ID!]
following:[ID!]
location:String!
}
Just think, if you could make User the input, how would you provide the mandatory ID? What if User implements an interface, since interfaces are not defined for inputs? Imagine if User had a recursive field type. In output, that would be fine (as you control the nesting level directly), but would be impossible to satisfy for an input type.
The rules for inputs and output are quite different, so the types need to be defined separately.
Also, in this example, only the IDs are optionally expected for the followers or the followed, instead of the full objects, as it often makes more sense.