This comment has been minimized.
This comment has been minimized.
@swalkinshaw Why is |
This comment has been minimized.
This comment has been minimized.
@felippenardi to represent a path to potentially nested fields (for input objects). |
This comment has been minimized.
This comment has been minimized.
@swalkinshaw - Great post, quick question how would you model a rule that depended on an aggregation of other objects. For example the parent object property would have a count or sum of a field in the subobjects. So if someone changes the property in the subobject, it must apply to be equal to the total count or sum in the parent object, otherwise it would violate the rule |
This comment has been minimized.
This comment has been minimized.
Very good content, thank you for posting this. |
This comment has been minimized.
This comment has been minimized.
Great content! Just one question: When speaking of the mutations return payload, you write:
Can you provide an example of such implementation? |
This comment has been minimized.
This comment has been minimized.
This is super helpful!! Thank you for sharing!! |
This comment has been minimized.
This comment has been minimized.
Great writeup! Appreciated! |
This comment has been minimized.
This comment has been minimized.
Awesome writeup! I think this is a fantastic baseline to follow. There are a couple rules though that are mentioned as a bit absolute, but I feel can be broken for the sake of performance or use case. Namely:
If I have a deeply nested object or static (does not change) object, then it feels like a basic optimization to fetch this data ahead of time and reuse what has already been retrieved. In my example, I have something to the effect of: type Klass {
uid: ID!
imageUrl: String
description: String
health: Int
mana: Int
power: Int
block: Int
}
type Hero {
uid: ID!
name: String!
klassUid: ID!
}
type User {
uid: ID!
heroes: [Hero]
parties: [String]
} In this case -- I have 20 heroes associated with my User and have the need to lookup other Users, I can dramatically reduce the payload size of each query by loading the render() {
const klass: IKlass = metadata.klasses[this.props.klassUid]
return {
<div>
<h1>{hero.name}</h1>
<img src={klass.image} />
<p>{klass.description}</p>
</div>
}
} Similarly, I have an Apollo GraphQL Server resolving Redis transactions for the in-game stateful information. I definitely don't want to store all of this information in a volatile Redis instance where I am paying a high dollar for in-memory storage. I would rather store in cheaply in MongoDB. The game server is on a separate cluster than the general API because I anticipate that I will need to have many more instances of the game server (Redis + Websocket over GraphQL Subscriptions) than I will of the API. Since there is this logistical separation, I can apply the same optimization as before for the Given this wall of text preface... Note: I just thought of C as I was writing this response. One issue I can see with option C though is that in the case of the game server example above, I don't have an active connection to Mongo -- only Redis. thoughts? |
This comment has been minimized.
This comment has been minimized.
@albertorestifo graphql-ruby has one: http://graphql-ruby.org/mutations/mutation_classes.html#example-mutation-class Those |
This comment has been minimized.
This comment has been minimized.
@stephencorwin option C seems good (at first glance). It actually kind of fits in with our Rule #13. |
This comment has been minimized.
This comment has been minimized.
@stephencorwin How about enabling the whole class data in the api and in the client using only the uid? (Option B as much as the api is concerned) |
This comment has been minimized.
This comment has been minimized.
"follow the same design patterns layed out here" should be "follow the same design patterns laid out here" |
This comment has been minimized.
This comment has been minimized.
@swalkinshaw @inbararan I ended up opting for option C and it seems to be working great! I am already storing only the What has been truly great though, is that with only a minor amount of effort, I implemented Apollo's Below are the results of this optimization for a User with 15 Heroes:
I think the thing I like most about this approach is that the API still provides both the Note: obviously, this is a small contrived example, but I believe the more Heroes requested, the more benefit you gain from this optimization. |
This comment has been minimized.
This comment has been minimized.
If business decides it needs a new field, does a developer have to go in and program it...everywhere? What I am asking is, for every change in the business, does a developer need to dig into the schema? Couldn't all this be automated in some way? Scott |
This comment has been minimized.
This comment has been minimized.
I'm not sure I understand your question @smolinari. You add a field to a type definition. That field then becomes available everywhere that type is being returned. So adding a field only requires adding it in one place. |
This comment has been minimized.
This comment has been minimized.
@smolinari You only need to define types on the server, the front-end dev can simply reference the types when writing queries and mutations on the client. Your graphql server is the source of truth. |
This comment has been minimized.
This is a fantastic write-up. I hope you consider including some variant of this as a blog post somewhere. I really appreciate explanations that take readers progressively from naive implementations that novices will likely attempt to more idiomatic approaches.