GraphQL Data Specification
14 |This document lays out GraphQL schema design principles & standards for building data APIs.
16 |This is ideal for GraphQL or federated GraphQL schemas that need to:
17 |-
18 |
- Provide a standardized API 19 |
- Provide a composable and fleixble design 20 |
- Balance domain-driven and consumer-driven design 21 |
- Support lightweight and high-velocity iterations on schema design 22 |
1Schema design principles
88 |When building a GraphQL schema, the schema design process should follow the following principles:
89 |-
90 |
- The GraphQL schema design process allows multiple stakeholders to own parts of the schema. 91 |
- Domain owners design their part of the GraphQL schema in a way that accurately reflects the domain. These might require keeping in mind the needs of multiple API consumers domain driven design. 92 |
- Consumers should be able to drive independent additions to the GraphQL schema that represent consumer specific needs. 93 |
- Any entity in the GraphQL schema should either represent a resource, a unit of data, or a method, a unit of business logic. 94 |
2Objects
98 |Every data object in the described domain, e.g., a User, an Order, or a Product in an e-commerce application, has a GraphQL type
.
2.1Example
101 |type User {
102 | user_id: Int!
103 | name: String
104 | }
105 |
106 | 3Global ID
110 |An id
field in the object can represent a globally unique id for the object. This allows the Object type to implement the Relay Node interface.
3.1Example
113 |type User implements Node {
114 | id: ID!
115 | user_id: Int!
116 | name: String
117 | }
118 |
119 | 4Selection (Models)
123 |Models are collections of objects that can be queried in standardized ways and represent standardized selection, query or read operations.
124 |4.1Single Object Selection
126 |If one or more fields can uniquely identify an object in a model, a root field should allow querying the object using those fields.
127 |4.1.1Example
129 |type Query {
130 | user_by_id(user_id: Int!): User
131 | }
132 |
133 | 4.2Multi Object Selection
137 |To fetch multiple objects, a root field should return a list of objects.
138 |4.2.1Example
140 |type Query {
141 | users: [User]
142 | }
143 |
144 | By default semantics, this should return all the objects in the model. Additional input paramenters can control the set of objects returned by the model.
145 |5Filtering
150 |A where
input parameter for a model allows querying a model based on some condition on one or more of its fields. This includes any relationship fields that have been defined on the object.
A particular field will support specific comparison operators. The where
input paramenter should support operators for AND / OR / NOT to compose a filter expression.
5.1Example
154 |type Query {
155 | users(where: User_boolean_exp): [User]
156 | }
157 |
158 | input User_boolean_exp {
159 | _and: [User_boolean_exp!]
160 | _or: [User_boolean_exp!]
161 | _not: User_boolean_exp
162 | user_id: Int_comparison_exp
163 | first_name: String_comparison_exp
164 | last_name: String_comparison_exp
165 | email: String_comparison_exp
166 | }
167 |
168 | input Int_comparison_exp {
169 | _eq: Int
170 | _lt: Int
171 | _gt: Int
172 | _lte: Int
173 | _gte: Int
174 | _in: [Int!]
175 | }
176 |
177 | input String_comparison_exp {
178 | _eq: String
179 | _in: [Int!]
180 | _like: String
181 | }
182 |
183 | 5.2Filter expression grammar
186 |6Sorting
208 |OpenDD can generate an input for sorting the objects when querying a model based on one or more of its fields. This includes any relationship fields that have been defined on the object. Multiple fields can be used as the sort key, with the order of specification determining which one gets applied first.
209 |6.1Example
211 |type Query {
212 | users(where: [User_order_by_exp]): [User]
213 | }
214 |
215 | input User_order_by_exp {
216 | user_id: OrderByDirection
217 | first_name: OrderByDirection
218 | last_name: OrderByDirection
219 | email: OrderByDirection
220 | }
221 |
222 | enum OrderByDirection {
223 | Asc,
224 | Desc
225 | }
226 |
227 | 7Pagination
231 |OpenDD can generate input arguments for paginating through the objects returned when querying a model.
232 |7.1Example
234 |type Query {
235 | users(limit: Int, offset: Int): [User]
236 | }
237 |
238 | 8Aggregation
242 |TODO: Add after v3 supports aggregation.
243 |9Relay
246 |The GraphQL API generated by OpenDD implements the Relay API and has a node
query root field that can be used to retrieve specific objects using their global ID.
9.1Example
249 |type Query {
250 | node(id: ID!): Node
251 | }
252 |
253 | 10Commands
257 |Commands are functions which take in some arguments and produce an output. The semantics of commands except for arguments and output are opaque.
258 |Commands can be made available for invocation either at the query root or the mutation root of the command GraphQL API.
259 |10.1Example
261 |type Mutation {
262 | validateDiscountCoupon(coupon_code: String): bool
263 | }
264 |
265 | 11Relationships
269 |Objects can be augmented with related information by defining a relationship from the object to a model or a command. This way, queries can be flexibly composed.
270 |Creating a relationship adds a relationship field to the generated object type which can be used to query the related model or command.
271 |11.1Example
273 |type Order {
274 | order_id: Int!
275 | product_id: Int!
276 | user_id: Int!
277 | user: User // relationship from Order.user_id to User.user_id
278 | }
279 |
280 | 12Query Composition
284 |For a has-many (array) relationship, the input parameters of multi-object model selection are available to use, so queries across relationships can be flexibly composed.
285 |12.1Example
287 |If there is a has-many relationship from User to orders and a has-one (object) relationship from Order to users:
288 |type User {
289 | user_id: Int!
290 | name: String
291 | orders(
292 | where: Orders_bool_exp,
293 | order_by: Orders_order_by_exp,
294 | limit: Int,
295 | offset: Int
296 | ): [Order]
297 | }
298 |
299 | Because relationships, filtering, and sorting are semantically meaningful in OpenDD, this allows for efficiently issuing such composable queries without resorting to N+1 database queries, even when the two related models come from different databases.
300 |12.2Example
303 |The following query results in only 2 database lookups, one for users and one for orders, as opposed to 11 lookups if resolving traditionally. Such efficient composition is impossible to achieve with generic resolver-based approaches even when using data loaders.
304 |query {
305 | users(limit: 10) {
306 | user_id
307 | orders(where: { total: { _gt: 100 } }, order_by: { order_date: DESC }) {
308 | order_id
309 | product_id
310 | }
311 | }
312 | }
313 |
314 | 13Filtering / Sorting Composition
318 |Relationship fields themselves can be used for filtering, sorting, and aggregating a model containing the source object.
319 |13.1Examples
321 |To query all users who ordered a particular product where orders are not a part of the users model:
322 |query {
323 | users(where: { orders: { product_id: { _eq: "product_1" } } }) {
324 | user_id
325 | }
326 | }
327 |
328 | To sort all orders of a particular product by the email of the user that placed the order, where the user email is not a part of the orders model:
329 |query {
330 | orders(where: { product_id: { _eq: 1 } }, order_by: { user: { email: ASC } }) {
331 | order_id
332 | }
333 | }
334 |
335 | This is possible since OpenDD has a semantic understanding of relationships and predicates. Doing this would be impossible in generic resolver-based approaches.
336 |340 | §Index
341 |-
342 |
- Field 343 |
- FieldComparisonExpression 344 |
- ModelBooleanExpression 345 |
- Where 346 |