will graphql cause more database IO and how to resolve it? - graphql

I know graphql can use resolver to get the real value of a field, but how to decide the right level of the field?
for example, I get a scheme like this:
type Query {
product(productId: String): {
id:String
title: String
price: Float
variant: {
id: String
title: String
}
}
}
Assuming the request just need title, variant's title,
if I write resolver for the whole product like below, there is the price field not use but I read it from database.This results in unnecessary database IO.
select * from product_table where id = productId
However, if I write resolver for each single field like below, It's a large work and not elegant design.
// title resolver
select title from product_table where id = productId
// price resolver
select price from product_table where id = productId
It's the problem of graphql and is there any way to solve it?

Related

GraphQL Tag dynamic table name in query (apollo)

In my app every customer has a own table for its data. Depending on which customer was selected I want to dynamically build the tablename for the Graphql query.
For example:
// Query for customer 1
gql`
query overviewQuery {
customer_1 {
aggregate {
count
}
}
}
`
// Query for customer 2
gql`
query overviewQuery {
customer_2 {
aggregate {
count
}
}
}
`
I have the customer id in my vuex store and want to insert a variable into the query like in the following pseudocode which is not working.
const tblUserAggregateName = `customer_${this.$store.state.customer.customerId`
gql`
query overviewQuery {
${this.tblUserAggregateName} {
aggregate {
count
}
}
`
Is there an option how to do this or how can this problem be solved?
It is not an option to hardcode all different customer queries and selected them on runtime.
Thanks!
In the answers it was mentioned, that it is against best practice to dynamically change the table name of GraphQL queries.
As changing the complete database structure for all clients (they each have a separate Database with a own PostgreSQL schema name) is not an option, I found a solution the looks a little bit hacky and definitely not best practice but I thought I might be interesting to share for others with the same problem.
The approach is pretty easy: I write the query as a simple string where I could use variables and convert them later to the gql AST Object with graphql-tag
const query = () => {
const queryString = `
{
${customerId}_table_name {
aggregate {
count
}
}
}`
return gql`${queryString}`
}
Works, but I you have a better solution, I am happy to learn!
You can supply variables, but they should not be used to dynamically infer schema object names. This violates core concepts of GraphQL. Try this instead:
gql`
query overviewQuery ($customerId: ID!) {
customerData (id: $customerId) {
tableName
aggregate {
count
}
}
}`
Apollo provides great documentation on how to supply variables into a query here: https://www.apollographql.com/docs/react/data/queries/
The backend can then use the customerId to determine what table to query. The client making requests does not need to know (nor should it) where that data is or how it's stored. If the client must know the table name, you can add a field (as shown in the example)

How to filter a Graphql query with a field value in Laravel 5.8 with Lighthouse

I have users who can create lists. Lists can be starred ("featured" if you prefer). I want to build a Graphql query in Lighthouse/Laravel that would retrieve all the starred/featured lists for a given user.
I tried this query and it works:
clistsByStarred(user_id: Int! #eq, starred: Boolean! #eq): [Clist] #all
and then I can use it like that:
query {
clistsByStarred(user_id: 1, starred: true) {
id
name
starred
}
}
But it feels like a dirty hack to me.
What I would like to achieve is this:
query {
user(id: 1) {
id
name
clistsByStarred {
id
name
starred
}
}
}
Any idea on how to implement it ?
type User {
id: ID!
clists(starred: Boolean #eq): Clist #hasMany
This example gives you a user type which has a list of Clist types.
You then just add a field in your query to get a single user by id.
When you query this, you can then specify the parameter starred to only get the starred.
For this to work the relationship has to exist on your eloquent model.
https://lighthouse-php.com/master/eloquent/relationships.html

combining resolvers for interface and concrete types

For some reason I'm having a hard time figuring out how to combine resolvers for a GraphQL interface and a type that implements said interface.
Say I have the following schema:
interface IPerson {
id: ID!
firstName: String!
lastName: String!
}
type ClubMember implements IPerson {
...IPerson fields
memberType: String!
memberSince: DateTime!
}
type StaffMember implements IPerson {
...IPerson fields
hireDate: DateTime!
reportsTo: StaffMember
}
extend type Query {
people(ids: [Int!]): [IPerson]
}
A full ClubMember query with all fields, such as:
query {
people(ids: [123456,234567,345678]) {
id
firstName
lastName
... on ClubMember {
memberType
memberSince
}
}
}
would produce a response like the following:
[
{
"id": 123456,
"firstName": "Member",
"lastName": "McMemberface",
"memberType": "VIP",
"memberSince": "2019-05-28T16:05:55+00:00"
},
...etc.
]
I've used makeExecutableSchema() from apollo-server with inheritResolversFromInterfaces: true, and I want to be able to make use of default resolvers for each interface/type by having the model classes backing IPerson, ClubMember, etc. return objects with only the fields relevant to each type, i.e., the model class for IPerson fetches only the fields required by IPerson, etc. That is, the response above would execute 2 SQL statements:
SELECT id, firstName, lastName FROM Contacts WHERE id IN(?);
and
SELECT contactId, memberType, memberSince FROM Members WHERE contactId IN(?);
Of course, I could get all the data in one SQL statement by doing a JOIN at the database level, but I really want to have one (and only one) way of resolving the fields required by IPerson, and let the other types augment that data with their own resolvers.
My question is, do I need to "join" the resulting objects together myself in the resolver for the people query type? E.g.
const resolvers = {
Query: {
people: function( parent, args, context, info ) {
let persons = context.models.Person.getByIds( args.ids );
let members = context.models.Member.getByIds( args.ids );
/*
return an array of {...person, ...member} where person.id === member.id
*/
}
}
}
Or is there some way that Apollo handles this for us? Do I want something like apollo-resolvers? The docs on unions and interfaces isn't super helpful; I have __resolveType on IPerson, but the docs don't specify how the fields for each concrete type are resolved. Is there a better way to achieve this with Dataloader, or a different approach?
I think this question is related to my issue, in that I don't want to fetch data for a concrete type if the query doesn't request any of that type's fields via a fragment. There's also this issue on Github.
Many thanks!
Edit:
__resolveType looks as follows:
{
IPerson: {
__resolveType: function ( parent, context, info ) {
if ( parent.memberType ) {
return 'ClubMember';
}
...etc.
}
}
}
This problem really isn't specific to Apollo Server or even GraphQL -- querying multiple tables and getting a single set of results, especially when you're dealing with multiple data models, is going to get tricky.
You can, of course, query each table separately and combine the results, but it's not particularly efficient. I think by far the easiest way to handle this kind of scenario is to create a view in your database, something like:
CREATE VIEW people AS
SELECT club_members.id AS id,
club_members.first_name AS first_name,
club_members.last_name AS last_name,
club_members.member_type AS member_type,
club_members.member_since AS member_since,
null AS hire_date,
null AS reports_to,
'ClubMember' AS __typename
FROM club_members
UNION
SELECT staff_members.id AS id,
staff_members.first_name AS first_name,
staff_members.last_name AS last_name,
null AS member_type,
null AS member_since,
staff_members.hire_date AS hire_date,
staff_members.reports_to AS reports_to
'StaffMember' AS __typename
FROM staff_members;
You can also just use a single table instead, but a view allows you to keep your data in separate tables and query them together. Now you can add a data model for your view and use that to query all "people".
Note: I've added a __typename column here for convenience -- by returning a __typename, you can omit specifying your own __resolveType function -- GraphQL will assign the appropriate type at runtime for you.

GraphQL with multiple objects: should I keep all objects in a single query?

I´m building a SaaS B2B application composed of several different objects. Examples:
Users
Customers
StockItens
StockLevels
PriceList
Sales
Returns
Etc...
I´ll have around 40 different objects, that can be listed and created, edited, and deleted individually.
Facing the GraphQL concepts for the first time, should I build a large schema for all objects, like the example below, or should I keep each object on its own query.
query {
viewer {
Users {
id
firstName
lastName
address
city
...
}
Customers {
id
firstName
lastName
address
city
rating
...
}
StockItens {
id
item_id
sales {
id
dateTime
qty
unitValue
totalValue
...
}
...
}
StockLevels {
...
}
PriceList {
...
}
Sales {
id
dateTime
qty
unitValue
totalValue
...
}
Returns {
...
}
}
}
Looking for the first option (keeping everything into one single query) seens logical as I will be using fragments to access the desired piece of information, but then I will have a huge schema with lots of inter relations.
PLease advice what would be the best practice on that use case.
I suggest you do not write a query where you add all needed data but use the concept of fragments as you already pointed out.
And you fetch only the data which are needed for the current page. So the throughput is kept minimal.
e.g.
If you have a page where you update a user you just fetch the needed data for this user in a specialized query. The query consists of fragments.
The fragments are related to the subcomponents which are used in the page, for example a form where you show the data of the user.
The fragment of the form defines the data it needs from the user and the update page combines the fragments to the query.
// in user form component
const userFormFragments = {
name: "UserForm",
document: `fragment UserForm on User {
id
name
}`
};
// in update user page
const userQuery = `query getUserQuery($userId: ID!) {
getUser(userId: $userId) {
...${userFormFragment.name}
}
${userFormFragment.document}
}`

GraphQL: Are either of these two patterns better/worse?

I'm relatively new to GraphQL, and I've noticed that you can select related fields in one of two different ways. Let's say we have a droids table and a humans table, and droids have an owner which is a record in the humans table. There's (at least) two ways you can express this:
query DroidsQuery {
id
name
owner {
id
}
}
or:
query DroidsQuery {
id
name
ownerId # this resolves to owner.id
}
At first glance the former seems more idiomatic, and obviously if you're selecting multiple fields it has advantages (owner { id name } vs. having to make a new ownerName so you can do ownerId ownerName). However, there's a certain explicitness to the ownerId style, as you're expressing "here's this thing I specifically expected you to select".
Also, from an implementation standpoint, it seems like owner { id } would lend itself to the resolver making an unnecessary JOIN, as it would translate owner { id } as the id column of the humans table (vs. an ownerId field which, with its own resolver, knows it doesn't need a JOIN to get the owner_id column of the droids table).
As I said, I'm new to GraphQL, so I'm sure there's plenty of nuances to this question that I'd appreciate if I'd been using it longer. Therefore, I was hoping for insight from someone who has used GraphQL into the upsides/downsides of either approach. And just to be clear (and to avoid having this answer closed) I'm looking for explicit "here's what is objectively bad/good about one approach over the other", not subjective "I prefer one approach" answers.
You should understand GraphQL is just a query language + execution semantics. There are no restrictions on how you present your data and how you resolve your data.
Nothing stops you from doing what you describe, and returning both owner object and ownerId.
type Droid {
id: ID!
name: String!
owner: Human! # use it when you want to expand owner detail
ownerId: ID! # use it when you just want to get id of owner
}
You already pointed out the main problem: the former implementation seems more idiomatic. No you don't make a idiomatic code, you make practical code.
A real world example as you design field pagination in GraphQL:
type Droid {
id: ID!
name: String!
friends(first: Int, after: String): [Human]
}
The first time, you query a droid + friends, and it is fine.
{
query DroidsQuery {
id
name
friends(first: 2) {
name
}
}
}
Then, you click more to load more friends; it hits DroidsQuery one more time to query the previous droid object before resolving the next friends:
{
query DroidsQuery {
id
friends(first: 2, after: "dfasdf") {
name
}
}
}
So it is practical to have another DroidFriendsQuery query to directly resolve friends from droid id.

Resources