Returning entity as Null

I am working on a proof of concept using Apollo Federation. I am running in to a bit of a snag and I’m not sure how to move forward. I’ll share a bit of my schema across to subgraphs and then explain my desired behaviour, and hopefully someone will be able to help out.

Subgraph 1:

type Offer @key(fields: "referenceId") {
  isAvailable: Boolean
  partner: String
  vertical: PreSelectionVertical
  amount: Float
  referenceId: String!
}

Subgraph 2:

type OfferCard implements DashboardCard {
  id: ID!
  title: String!
  createdAt: DateTime!
  updatedAt: DateTime!
  partners: [Offer]!
}

extend type Offer @key(fields: "referenceId") {
  referenceId: String! @external
  cardCopy: CMSOfferProduct
}

type CMSOfferProduct @key(fields: "id") @key(fields: "referenceId") {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime!
  partner: String!
  referenceId: String!
  titleCopy: String!
  bodyCopy: String!
  ctaCopy: String!
}

We have a resolver the returns a list of DashboardCard objects, our OfferCard contains a list of referenceId referencing the offers we would like to render on this card in their preferred order. So in my resolver for OfferCard we return, essentially:

OfferCard: () => {
    return {
        ...theRestOfOurFields,
        partners: [{referenceId: "1"},{referenceId:"2"}
    }
}

then in our resolver for Offer in __resolveReference we make an API call for the referenceIDs that are received, and return all of the fields if present, or if the offer isn’t available for the current user we return Null.

The problem is when an offer is not available for the current user, what we are receiving in our GraphQL response is:

products: [{
      "referenceId": "1",
       "vertical": null,
       "partner": null,
       "isAvailable": null,
}]

Is it possible to, instead of have all the fields set to null and the referenceId to be defined, to have the whole object be set to null? Or, better yet, have a union on subGraph2 that is a union of two entities that exist on subGraph1 i.e union OfferResponse = Offer | OfferNotValid`

Hoping someone has a tip for how to resolve this! Thanks.

If I set any of the optional fields to required in Offer then I do receive null as desired, however it is accompanied by graphql errors which is also not desired.

What you are running into is more of a data synchronization issue here, not a Federation issue. Ideally the check to return valid referenceIds would happen at the first subgraph so you can always resolve a field given it’s id, however I can understand either due to legacy tech stacks or other reasons how you ended up here.

To answer your question, no, as of today there is not a way to remove the referenceId from the data. If we have the data Federation will always return it. We leave it up to the users to determine if the data is valid or not so there is not a way today to null out existing data.

But there are work arounds, so here are a couple options

  1. First add the logic to check valid offers first in subgraph 1 and return only a list of valid ids or even an empty list or null in the partners resolvers. This is really where the biz logic needs to happen to know what is a valid partner offer at this given time.

  2. Keep what you have but update the schema to be a bit more nested so clients have to check a nullable field first to get product info

type Offer @key(fields: "referenceId") {
  offerData: OfferData # contains logic you want to add in this resolver so it can be null if it should be ignored
 # isAvailable: Boolean # Maybe this could live here to as another field clients to check for a valid offer 
  referenceId: String!
}

type OfferData {
   isAvailable: Boolean
  offerData: OfferData
  partner: String
  vertical: PreSelectionVertical
  amount: Float
}
  1. In Fed 2, You can hide the id behind a second id that contains the real logic and is required to extended
type Offer @key(fields: "validOfferId") @key(fields: "referenceId") {
  referenceId: String! @inaccessible # Hide this field from clients, should only be internal
  validOfferId: String # Is just a copy of id but can be null so you can return an null reference if needed and extend based on this id now
  isAvailable: Boolean
  partner: String
  vertical: PreSelectionVertical
  amount: Float
}