I’m using Spring for GraphQL and having trouble understanding how to make my graphql resolvers avoid over- and under- fetching without modifying my schema to reflect how data is grouped in my database.
I’ve read about the N+1 problem but I think this is slightly different. Consider the following graphql schema:
type Query {
employee(name: String, companyName: String): Employee
}
type Employee {
name: String
companyName: String
age: Int # source: Person table
address: String # source: Person table
jobTitle: String # source: Employee table
startDate: String # source: Employee table
}
Is there a way to set up resolvers to always fetch age
and address
together? And to fetch jobTitle
and startDate
together?
I could modify the schema to group those fields, but then it is tightly coupled to my database:
type Employee {
name: String
companyName: String
personalDetails : PersonalDetails # source: Person table
jobDetails: JobDetails # source: Employee table
}
type PersonalDetails {
age: Int
address: String
}
type JobDetails {
jobTitle: String
startDate: String
}
As an example, the following code uses four resolvers to populate Employee, but I’d like to just use two:
@Controller
public class GraphqlController {
record Employee(String name, String companyName, Integer age, String address, String jobTitle, String startDate) {
}
@QueryMapping
public Employee employee(@Argument String name, @Argument String companyName) {
return new Employee(name, companyName, null, null, null, null);
}
@SchemaMapping
public Integer age(Employee employee) {
return 35;
}
@SchemaMapping
public String address(Employee employee) {
return "US";
}
@SchemaMapping
public String jobTitle(Employee employee) {
return "Software";
}
@SchemaMapping
public String startDate(Employee employee) {
return "Monday";
}
}
1 Answer
Right now your example is a bit artificial in that your field resolvers are returning static values. In a real system your resolvers are going to be accessing a database.
A GraphQL type will typically map fairly closely to a table or document collection. It doesn’t have to but the main point is that a db query can return multiple columns at a time.
Let’s say your Employee
type maps to one or two tables in a relational database. When a resolver is resolving one or more Employee
records it can do:
select * from Employee where id=123;
If your GraphQL field names are the same as the underlying database column names then your Employee
resolver can just return the record as is. GraphQL will take care of removing any fields that were not in the original request. If a particular field name does not match the underlying column name then you can write a simple field resolver that just renames the column. Remember: the parent
object that is passed to your field resolver will be the entire db record including all the original columns.
Where things get more interesting is when your GraphQL fields map to other tables. That’s when the N+1 problem comes up but where you also get the benefit of GraphQL only running resolvers for fields that have been requested in the query. If you have a complex resolver but the client isn’t requesting that data, that resolver will not run.
Some time ago I published a post which goes into a lot of this. Although the post uses JS instead of Java, the rdb-GraphQL mapping concepts still apply.