How would I assign meal event guests per attendee per table per event at a conference in a GraphQL schema? - graphql

I'm pondering a rebuild of a meal seating assignment app. I'll make use of GraphQL to query for attendees and their guests of a table at a meal event (assigned seating). The delivered data model looks something like this:
meal event 1
table A
attendee 1
guest 1
...
meal event 2
table L
attendee 1 (no guest this time)
...
An attendee may attend multiple meal events e.g. at a multi-day conference. That attendee may have a guest(s) at meal event(s). This could be another attendee record (two meal tickets, same info), noted as a guest however as an attendee and their guest(s) need to be seated at the same table (don't want spouse on other side of the room for example). Attendees will usually have the same guest(s) at multiple events, however not always due to cost or conflicting guest commitments or wanting to rotate guests per meal, etc.
How would I model this in a GraphQL schema? In the database I'd use join tables. However for client-facing GraphQL queries and schema types, I need to model this a bit differently (I think?). Here's what I have so far:
type Attendee {
name: String
** Can't add guests here as this may change per event
}
type Event {
name: String
tables: [Table]
}
type Table {
name: String
attendees: [Attendee]
** How do I "tree" in guests here for attendees for this table?
}
At this point do I need a join type to get guests per attendee per table per event?

When writing our server code, we typically encapsulate our business logic inside domain models like Event, Table, Attendee or Guest. However, these domain models don't have to have a strict one-to-one mapping with the tables we use to persist their data -- for example, an individual model might aggregate data from multiple tables.
Similarly, your GraphQL types do not have to have a one-to-one mapping to your domain models. It can often make sense to represent the same domain model as multiple types. For example, we can do something like:
type EventAttendee {
id: ID!
name: String!
}
type TableAttendee {
id: ID!
name: String!
guests: [Guest!]!
}
type Table {
id: ID!
attendees: [TableAttendee!]!
}
type Event {
id: ID!
name: String!
attendees: [EventAttendee!]!
tables: [Table!]!
}
Your schema effectively represents multiple graphs or views of your data. Depending on where on that graph your domain model ends up should determine what fields it exposes as a type -- in some contexts, exposing certain properties or relationships makes sense, in others it does not.

Related

Architecture for avoiding repeated data in GraphQL

I have a application where the same data is present in many places in the graph and need to optimize the data queries to avoid processing and sending the same data too often.
As an example consider the following pseudo schema:
type Group {
name: String
members: [Person]
}
type Person {
name: String
email: String
avatar: Avatar
follows: [Person]
followedBy: [Person]
contacts: [Person]
groups: [Group]
bookmarks: [Bookmark]
sentMessages: [Message]
receivedMessages: [Message]
}
type Message {
text: String
author: Person
recipients: [Person]
}
type Bookmark {
message: Message
}
Querying a users data can easily contain hundreds, if not thousands, of Person-objects even though it the small circle of friends/contacts/follows only contains tens of distict users.
In my real implementation about 80% of each GraphQL query (in bytes) is redundant and considering that the client does many different queries in the same space above 90% of all data transferred and processed is redundant.
How could I improve the model so that I don't have to load the same data again and again without complicating the client too much?
I'm using Apollo for both GraphQL client and server.
Use/implement pagination (instead of just arrays) for relations - this way you can query for count/total (render it without array processing) and array of ids only - usually there is no need to query/join person table (DB) at all.
Render list of Person components (react?) using passed id prop only ... only rendered Person fetches for more details (if not cached, use batching to merge requests) consumed/rendered inside.

Is there a way to compare each item to a aggreated value?

I'm new to graphQL and Hasura. I'm trying(in Hasura) to let me users provide custom aggregation (ideally in the form of a normal graphQL query) and have then each item the results compared against the aggreation.
Here's a example. Assume I have this schema:
USERTABLE:
userID
Name
Age
City
Country
Gender
HairColor
INCOMETABLE:
userID
Income
I created a relationship in hasura and I can query the data but my users want to do custom scoring of users' income level. For example, one user may want to query the data broken down by country and gender.
For the first example the result maybe:
{Country : Canada
{ gender : female
{ userID: 1,
Name: Nancy Smith,..
#data below is on aggregated results
rank: 1
%fromAverage: 35%
}...
Where I'm struggling is the data showing the users info relative to the aggregated data.
for Rank, I get the order by sorting but I'm not sure how to display the relative ranking and for the %fromAverage, I'm not sure how to do it at all.
Is there a way to do this in Hasura? I suspected that actions might be able to do this but I'm not sure.
You can use track a Postgres view. Your view would have as many fields as you'd like calculated in SQL and tracked as a separate "table" on your graphql api.
I am giving examples below based on a simplification where you have just table called contacts with just a single field called: id which is an auto-integer. I am just adding the id of the current contact to the avg(id) (a useless endeavor to be sure; just to illustrate...). Obviously you can customize the logic to your liking.
A simple implementation of a view would look like this (make sure to hit 'track this' in hasura:
CREATE OR REPLACE VIEW contact_with_custom AS
SELECT id, (SELECT AVG(ID) FROM contacts) + id as custom FROM contacts;
See Extend with views
Another option is to use a computed field. This is just a postgres function that takes a row as an argument and returns some data and it just adds a new field to your existing 'table' in the Graphql API that is the return value of said function. (you don't 'track this' function; once created in the SQL section of Hasura, you add it as a 'computed field' under 'Modify' for the relevant table) Important to note that this option does not allow you to filter by this computed function, whereas in a view, all fields are filterable.
In the same schema mentioned above, a function for a computed field would look like this:
CREATE OR REPLACE FUNCTION custom(contact contacts)
RETURNS Numeric AS $$
SELECT (SELECT AVG(ID) from contacts ) + contact.id
$$ LANGUAGE sql STABLE;
Then you select this function for your computed field, naming it whatever you'd like...
See Computed fields

Data modelling for ecommerce website using Amplify + GraphQL + DynamoDB

I'm using Amplify from AWS to build a small ecommerce project using React as frontend.
I'd like to know how I should write the "Product" and "Order" types in the schema in order to be able to write productId's to a product array in the Order table when users complete a purchase.
My schema.graphql file:
type Product #model {
id: ID!
name: String!
price: Int!
category: String!
images: [String]!
}
type Order #model {
id: ID!
products: [Product] #connection
}
My question is about the last line, do I need to define that [Product] connection there or I can use [String] to store product id's in a simple string array?
Point 1: In dynamoDB, you only need to define the data type of your partition key and sort key, and these can be string, number etc. For all the other attributes, you don't need to define anything.
Point 2: The dynamoDB designers prefer using a single table per application, unless it's impossible to manage data without multiple tables. Keeping this in mind, your table can be something like this.
Please observe: Only Id aka partition key and Sk aka sort key column is fixed here, all other columns can be anything per item. This is the beauty of DynamoDB. Refer to this document for dynamoDB supported data types.

How to encapsulate data of a type to be specific to the parent type in graphql

I have this game type:
type Game {
id: ID! #id
goals: [Goal]
}
which have a Goal relationship to:
type Goal {
id: Int! #id(strategy: SEQUENCE) #sequence(name: "IncID", initialValue: 1, allocationSize: 20)
thumbnail: String!
player: String!
minute: Int!
}
what i'm trying to do by that "id" mess is to create an incremental id value for the goal, for the purpose of creating a url for each goal, like this:
domaine.com/game/{id-of-the-game}/goal/{incremental-id(1,2..)}
the problem is, the Goal type looks like it is an entity of its own, it is gonna keep the last incremented id even if it is new game.
so i want to reset the id sequence for each new game.
What you ask for is not possible using the #id annotation. Each type in the prisma model needs to have a unique id to identify the object in the database. If the underlying database used is MongoDB there will be a Goal collection with documents in it, each representing an individual Goal identified by the id. If the underlying database used is MySQL/PostgreSQL the Goals will be stored in a Goal table with each row representing an individual Goal.
Each individual object (no matter if it is stored as a document or row) needs to be uniquely identified to access it and to create relations, e.g. between Goal objects and Game objects.
If the Goal id would start at 1 for each Game this would violate the unique constraint for the id field since two Goals in the table or collection would be identified by the same id (e.g. 1).
What I would suggest is to simply add something like a "numberInGame" field to the Goal type and fill it while creating the Goal (e.g. by taking goals.length in the Game.type into consideration).
Hope that helped to clarify the id field uniqueness constraint.

Using GraphQL with conditional related types

I have an app that has a type with many related types. So like:
type Person {
Name: String!
Address: Address!
Family: [Person!]!
Friends: [Person!]!
Job: Occupation
Car: Car
}
type Address {...}
type Occupation {...}
type Car {...}
(don't worry about the types specifically...)
Anyway, this is all stored in a database in many tables.
Some of these queries are seldom used and are slow. Imagine for example there are billions of cars in the world and it takes time to find the one that is owned by the person we are interested in. Any query to "getPerson" must satisfy the full schema and then graphql will pare it down to the fields that are needed. But since that one is slow and could be requested, we have to perform the query even though the data is thrown out most of the time.
I only see 2 solutions to this.
a) Just do the query each time and it will always be slow
b) Make 2 separate Query options. One for "getPerson" and one "getPersonWithCar" but then you're not able to reuse the schema and now a Person is defined twice. Once in terms of the car and once without.
Is there a way to indicate whether a field is present in the Query requested fields? That way we could say like
if (query.isPresent("Car")) {
car = findCar();
} else {
car = null;
}

Resources