Coder Social home page Coder Social logo

nullability-wg's Introduction

GraphQL Nullability Working Group

This working group is a subcommittee of the GraphQL Working Group with the following primary purpose:

Giving clients a way to influence or override nullability checks within queries.

Anyone in the public GraphQL community may attend a meeting of this subcommittee, provided they first sign the Specification Membership Agreement or belong to an organization which has signed.

This repository holds agendas and notes for all meetings past and upcoming as well as shared RFC documents. Anyone may edit an upcoming event's agenda to attend or propose an agenda item.

All meetings occur via video conference, however participating company offices are welcome to host guests.

Meetings are on the last Wednesday of every month. Check the agendas for the exact date and time of upcoming meetings.

Keep track of future upcoming meetings by subscribing to the Google Calendar or ical file. (maintained in UTC because time zones are hard).

Joining a meeting?

To request participation in an upcoming meeting, please send a pull request by editing the relevant meeting agenda.

Want to help us keep up?

We're always looking for volunteers to help take notes from the meetings, the results of which are shared in notes/. If you're interested in taking notes, sign up for a meeting in agendas/ and indicate that you're willing to be a note taker.

Participation guidelines

Meetings with many participants, especially over video, can easily get hard to follow or run off course. When we talk about issues we care about, it's easy to get into heated debate. In order to respect everyone's time, and arrive to worthwhile outcomes, this subcommittee follows the "Participation guidelines" in the GraphQL Working Group; a summary of which is:

  • Participate
  • Don't talk too much
  • Volunteer to take notes
  • Have an outcome in mind
  • Contribute
  • Choose your battles
  • Champion alternatives
  • Block progress as a last resort
  • Be patient and persistent

Contributing to this repo

This repository is managed by EasyCLA. Project participants must sign the free (GraphQL Specification Membership agreement before making a contribution. You only need to do this one time, and it can be signed by individual contributors or their employers.

To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you.

You can find detailed information here. If you have issues, please email [email protected].

Commit access

Commit access is granted to this repo to members of the GraphQL TSC and some regular attendees of subcommittee meetings. To request commit access, please reach out to a TSC member.

Resources

nullability-wg's People

Contributors

anthonymdev avatar aprilrd avatar benjie avatar bod avatar calvincestari avatar captbaritone avatar ernieturner avatar fotoetienne avatar martinbonnin avatar mjmahone avatar twof avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nullability-wg's Issues

[2023-09-26] Update Interrobang RFC... to remove the interrobang 😉

Changes:

  1. In the SDL, we add a question mark, this has the behavior of the field being expressly nullable
  2. Anything that does not have a question mark is not supposed to be nullable ie if there is no value, then there MUST be an error in the errors array.
  3. Introspection part: A flag that would indicate that a client is aware of this behavior and gets introspection that’s aware of this nullability

Note: Action Item issues are reviewed and closed during Working Group
meetings.

CCN: A path to True Nullability Schema

TL;DR I see a path where CCN’s ? could be leveraged by smart clients to safely expose the true resolver-nullability of fields directly to product code.

Prelude: CCN Behavior Definition

Since Client Controlled Nullability (CCN) may have a different meaning to different people, I’ll start by specifying my hope for how CCN will work. The rest of this post assumes this behavior:

Under CCN, the ! and ? annotations would allow the query to override the schema nullability of a field within a selection for the purposes of the execution of that selection.

  • ! means: Treat this field, in this selection, as if it were non-nullable
  • ? means: Treat this field, in this selection, as nullable

In other words, for the purposes of executing a query selection, every place the spec refers a field’s schema nullability, it would instead refer to the field’s nullability within the selection, which may or may not have been modified by CCN annotations in the query. Beyond that, all error handling and null bubbling behaviors of the current spec would be unchanged. Note that this includes the fact that errors thrown by a ? field would still be included in the response errors metadata.


Client-defined resiliency

GraphQL’s current recommended approach to providing response resiliency in the face of resolver errors is to make fields in the schema nullable by default. Unfortunately, this has the effect of obscuring the true nullability of fields. Clients, and even users, can’t tell from the schema alone if null is expected as a possible value, or if the field will only return null in exceptional (error) cases.

In this world of nullable-by-default schemas, the Client Controlled Nullability (CCN) proposal is primarily a tool to add assertions via !. While this is a marked ergonomic improvement, these assertions must be added blindly, without knowing if null is an expected value or not. This is at best awkward and at worst dangerous.

However, CCN’s ?, opens up the up the possibility of a different mechanism to achieve request resiliency. One which avoids obscuring the true nullability of fields. Specifically, an approach where we shift expectation from “it is the server/schema’s responsibility to make requests resilient to errors by typing fields as nullable” to “it is the client/queries responsibility to make requests resilient to errors by annotating non-nullable fields with ?”.

With this approach to resiliency, the schema could specify the “true” nullability of the fields.

For simplistic clients, e.g. Curl, the client/user can now see the true nullability of each field in the schema and add the appropriate amount of resilience for their use case using CCN’s ?. In a sense this is the same as CCN’s ! applied to a fully nullable schema, in that the client is empowered to declare which fields it can manage without, and which fields it requires. We’ve “simply” inverted the default. Of course, defaults are tremendously powerful and this tradeoff should be considered carefully. See “The power of defaults” below.

The opportunity for smart clients

For smart clients, this approach can not only let users “see” the true nullability, it can actually let product code interacted with generate types that model this true nullability. I see this as a fundamental solution of the actual problem that CCN initially set out to solve.

If smart clients can transform errored fields into contained thrown exceptions, that would mean product code should never encounter a null value due to a resolver error. In that case, the types that the smart client generates for its fragments/queries could safely express the true nullability of those fields on the server.

This is something that we are currently, actively exploring for Relay. I’d encourage you to read the linked issue, but in short rather than containing errors with null bubbling, we contain errors with error boundaries. For cases where the user wants to imperatively handle the error case, they may add a @catch directive to the field which behaves very similarly to CCNs ?` and would hopefully some day be subsumed by it.

Note that compiler-based smart clients like Relay transform the queries/fragments defined by the user before sending them to the server. This means Relay can auto-insert ?s on all non-nullable fields, ensuring resiliency is the default behavior and we will always render as much of the UI as possible, given the data that the server was able to send.

So, Relay would use ? in two different was:

  1. As a hidden implementation detail used to ask the server to not apply null bubbling
  2. As a user-facing feature to allow components to locally handle errors instead of relying on error boundaries

A pattern not a feature

One appealing aspect of this vision is that it’s simply composed from existing, or at least proposed, GraphQL spec primitives. It does not require any additional spec changes, and can be optionally adopted by those who find it a good tradeoff.


Appendix/Caveats

This solution is not a silver bullet. It may not be viable for other clients, and even for Relay there are significant challenges that would need to be solved first. I propose it here more as a long-term vision than as an immediate next step. Here are list of concerns/caveats/complicating factors:

Missing Data

In Relay, there are actually two reasons that we type all fields as optional:

  1. The field might return null due to error
  2. The field might be missing due to normalization

To make Relay fields non-nullable by default, we’ll need to first provide a mechanism for Relay to handle refetching (or erroring) in the face of missing data. I believe missing data is a fundamental gap in Relay today and is deserving of a project to resolve that gap.

The power of defaults

Shifting responsibility from the server to the client makes it harder to enforce this best practice of resiliency. Opinionated smart client frameworks may be able take over the role of enforcing resiliency by auto-inserting ?s, but the story for simplistic clients is less clear.

Users will instinctively take the path of least resistance. If adding resiliency is extra work that is not forced upon them by the server or a client framework, it is likely that client code will tend not to go the extra mile to handle potential errors.

Error boundaries

This approach is dependent upon having a client architecture that allows product code to contain errors thrown during render. React Error Boundaries provide this primitive, but client architectures without such a feature may not have a clear path to adding explicit error handling, which is a necessary ingredient for this approach to work.

Even in Relay, explicit error handling has not yet been validated, though we hope to ship it to production soon.

Breaking changes

Another reason that GraphQL recommends that all fields be nullable, even if their current implementation is non-nullable, is that it allows us to turn a non-nullable field into a nullable field as a non-breaking change. This is especially important on mobile where clients live essentially forever. Being able to make a field nullable can be key to being able to delete code.

I don’t have a solution to this problem, but I am curious to learn how well it works in practice. Have users of this approach actually be able to routinely make fields nullable without breaking old clients? Are product engineers really designing apps that gracefully degrade in the face of any field being null? The convergent evolution of @required and CCN’s ! makes me wonder.

Worst case, the approach I outline here would only be viable for clients with a finite support window.

Alternatives to CCN

Our use of CCN to enable this new model, is more opportunistic than designed. CCN offers primitives that smart clients can leverage behind the scenes as a compiler implementation detail. The core behavior we really want is:

  1. A schema that exposes the true nullability of fields, at the same time as…
  2. An execution model that performs no null bubbling

This works because we can expect the smart client to intercept error fields before they reach product code, shielding it from nulls in non-nullable locations.

If we think this model is broadly valuable, it’s possible we would want to explore a more explicit mechanism to enable this execution model rather than simply allowing smart clients to fake this execution model via compiler-inserted CCN annotations.

CCN’s ? can save normalized caches from null bubbling corruption

TL;DR: I believe smart clients like Relay, which maintain a normalized cache, could leverage CCN’s ? to prevent cache corruption due to null bubbling

The Problem

Null bubbling (when a error or null value encountered in a non-nullable field bubbles up to a parent nullable field) is destructive. It causes true and correct data that could be included in the response to be omitted. This can cause problems when trying to write a GraphQL response into a normalized cache.

Imagine two cards A and B side by side, each of which contain information about the current user. A shows “name” and B shows “age”. They are powered by two different queries. The response for query A looks like this:

{ // Response A
  "data": {
    "me": { "id": "10", "name": "Jordan}
  }
}

We can write this into our normalized cache and it will look like:

{ // Normalized cache
  "ROOT": {
    "me": {__id: "10"}
  },
  "10": {
    "id": "10",
    "name": "Jordan"
  }
}

This is fine, and we can nicely render our first card.

Now, however, we get an error in our second query’s response:

{ // Response B
  "data": {
    "me": null // <-- Null due to bubbling
  }
  "errors": [{ "message": "Age errored", "path": ["me", "age"] }]
}

The age field, which was non-nullable, has errored, and we won’t be able to render our second card. Oh well, such is life. However, when we go to write this into our cache, things get worse:

{ // Normalized cache
  "ROOT": {
    "me": null // <-- We had to write null here!
  },
  "10": {
    "id": "10",
    "name": "Jordan"
  }
}

Now card A, which also reads from this normalized store, is broken despite the fact that none of the fields it reads are in an error state.

A possible solution

With the adoption of Relay’s proposed error handling feature, product code is shielded from implicit field errors via framework-level explicit error handling. This means Relay is no-longer dependent on the server’s response shape strictly matching the schema. Specifically, it’s fine for a field marked as an error to be missing, even if it’s non-nullable. In other words, Relay can safely opt-out of server-side null bubbling.

And CCN's ? offers Relay just that option. Relay’s compiler can annotate every non-null field in the query it generates with a ? to opt out of any null bubbling.

Note: I’ve documented the CCN behavior I’m expecting here.

A new world

Lets now imagine how response B would look like in this new world:

{ // Response B (without null bubbling)
  "data": {
    "me": {
      "id": "10",
      "age": null // <-- Errored, but did not bubble!
    }
  }
  "errors": [{
    "message": "Age errored",
    "path": ["me", "age"]
  }]
}

When we write this into our normalized cache we get:

{ // Normalized cache
  "ROOT": {
    "me": {__id: "10"}
  },
  "10": {
    "id": "10",
    "name": "Jordan"
    "age": Error("Age errored")
  }
}

Card B still can’t render, but note that we’ve prevented our normalized cache from getting corrupted due to null bubbling. Thanks CCN!

Related

This post is spiritually related to #19 in that they both explore the benefits of a mode of GraphQL execution that avoids null bubbling. It may be worth exploring other mechanisms where-by clients could opt out of null bubbling, but I wanted to point out that CCN’s ? is a powerful enough primitive to allow a smart client like Relay to opt out of null bubbling.


Hat tip to @RyanHoldren who wrote an excellent internal post articulating this normalization issue. My note here is very much inspired by that post.

Rename Working Group to "Nullability Working Group"

Context:
This working group set out to solve the issues discussed in the original Client Controlled Nullability RFC however, the solution to the problem has evolved past the solution described in that RFC to include the solutions described in the the True Schema Nullability discussions. For the time being CCN is being put on pause so that efforts can be redirected to True Schema Nullability.


Note: Action Item issues are reviewed and closed during Working Group
meetings.

Write up concerns regarding the try/catch method proposed for CCN

Benjie expressed concerns about the try/catch interpretation of CCN and would prefer to just use the mechanisms that GraphQL already has. He intends to write up some examples to demonstrate the issues that he foresees.


Note: Action Item issues are reviewed and closed during Working Group
meetings.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.