Prisma1: Rethink non-null fields

Created on 9 Dec 2017  路  4Comments  路  Source: prisma/prisma1

Intro

This post by Caleb Meredith does a good job of explaining why non-null fields should often be avoided https://medium.com/@calebmer/when-to-use-graphql-non-null-fields-4059337f6fc8

This issue lists all places where Graphcool could expose non-null fields and provides the rationale for either exposing them as nullable or non-null fields.

Why non-null fields are bad

Hard to evolve schema

GraphQL schemas are often meant to be consumed by multiple applications, sometimes created by independent teams inside or outside the company. This makes it extremely difficult to change the schema in a way that could break application logic. Making a non-null field nullable is such a change.

Graphcool will usually be fronted by a single server that transforms the schema, performs authorization etc, somewhat alleviating this point.

Null-bubbling Might mask valid data

A GraphQL API will never return null for a non-null field. Instead the null value bubbles up the hierarchy to the nearest nullable field.

If a field has the type [Int!] and the returned list contains a single null, the entire field will be null, even if the list contained valid values.

Graphcool is in control of data going into the database, so we can make smart decisions about when it is safe to expose a non-null field. This is somewhat complicated by the fact that we allow application developers direct access to the database, so it is possible for the underlying data to become invalid at any point.

Graphcool

Consider this schema

type Post {
  id: ID! @unique
  title: String!
  subTitle: String
  comments: [Comment]
  author: User!
}

# User and Comment types omitted for brevity

data argument on mutations: scalar values

update

All data fields are optional

create

required scalar fields are required:

input UserCreateInput {
  title: String!
  subTitle: String
  # relation fields
}

nested mutations

update

All nested mutation fields are optional

create

required relation fields are required:

input UserCreateInput {
  comments: CommentCreateManyInput
  author: UserCreateOneInput!
  # scalar fields
}

As detailed in #1341 the fields in nested mutation types are all optional, as there are many valid options. We return a runtime error if these fields are used in invalid combinations.

Note that only one-relations can be required.

Model query types

The above Post type generates the following type:

type Post {
  id: ID! @unique
  title: String!
  subTitle: String
  comments: [Comment]
  commentsConnection: CommentConnection!
  author: User
}

required scalar fields

The underlying database guarantees that required fields are non-null, so we can safely expose this.

The one exception is that data written directly to the database might have a format that is incompatible with the GraphQL type. When this happens, the database is considered to be in an inconsistent state. We will introduce tools to help developers to recover from this situation.

required one-relation

Required relations are not enforced by the underlying database, so at this time we should not make the field required.

Connections

We make the connection field required and guarantee that it will always be returned successfully. All sub fields are optional except edges which is guaranteed to always be there. The Edge is optional, preventing any null bubbling.

On the Edge type, both node and cursor fields are non-null. If for any reason we fail to return either of them, we simply bubble the error up to the optional Edge.

type CommentConnection {
  edges: [CommentEdge]!
  aggregate: CommentAggregate
  group: CommentGroupBy
}

type CommentEdge {
  node: User!
  cursor: String!
}

list-relations

For the reasons laid out by caleb we will make elements in list relations optional. This prevents a single invalid node to bubble up and render the entire list unavailable:

comments: [Comment]!

Ideally we would like to also make the node in the list required, and we might choose to implement this change in the future:

comments: [Comment!]! # We might adopt this in the future
rf0-needs-spec

Most helpful comment

I think Caleb Meredith actually did a terrible job with his reasoning about non-null fields, and I don't agree with any of the arguments.. Apart from that, even though some users might adapt some of the points from that article, I see absolutely no reason why the generated schema should deviate from what I, the developer, specify on my type definitions.

If I specify author: User!, why would the generated schema have author: User. If I wanted it to be nullable in the first place, I would have put author: User in my type definition instead. It's also very inconsistent, because the author field on the input type is required: author: UserCreateOneInput!.

All 4 comments

I think Caleb Meredith actually did a terrible job with his reasoning about non-null fields, and I don't agree with any of the arguments.. Apart from that, even though some users might adapt some of the points from that article, I see absolutely no reason why the generated schema should deviate from what I, the developer, specify on my type definitions.

If I specify author: User!, why would the generated schema have author: User. If I wanted it to be nullable in the first place, I would have put author: User in my type definition instead. It's also very inconsistent, because the author field on the input type is required: author: UserCreateOneInput!.

I see this is no closed, after being tagged for beta2, but without any information on what was decided.

I'd also like to know what is the plan here. I think non-null is a feature that developers should be able to chose to use if they understand the tradeoffs however Prisma could make improvements on the null values handling in non-null fields, for example:

Returning null for an entire array when just one item has an unexpected null value is not what I'd expect as a developer, instead I'd expect the array to contain the valid items and maybe highlight that some specific item encountered a fatal error.

You can use the xsConnection queries, that expose an required edges array of optional nodes. If one node is null, that doesn't "propograte" to the entire list in this case.

Does that resolve your problem, @tomsdev?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

schickling picture schickling  路  3Comments

schickling picture schickling  路  3Comments

akoenig picture akoenig  路  3Comments

AlessandroAnnini picture AlessandroAnnini  路  3Comments

marktani picture marktani  路  3Comments