Skip to content

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

@captbaritone

Description

@captbaritone

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions