Orodan
Orodan

Reputation: 1047

Resolve to the same object from two incoherent sources in graphql

I have a problem I don't know how to solve properly.

I'm working on a project where we use a graphql server to communicate with different apis. These apis are old and very difficult to update so we decided to use graphql to simplify our communications.

For now, two apis allow me to get user data. I know it's not coherent but sadly I can't change anything to that and I need to use the two of them for different actions. So for the sake of simplicity, I would like to abstract this from my front app, so it only asks for user data, always on the same format, no matter from which api this data comes from.

With only one api, the resolver system of graphql helped a lot. But when I access user data from a second api, I find very difficult to always send back the same object to my front page. The two apis, even though they have mostly the same data, have a different response format. So in my resolvers, according to where the data is coming from, I should do one thing or another.

Example :

API A 

type User {
   id: string,
   communication: Communication
}

type Communication {
   mail: string,
}
API B

type User {
   id: string,
   mail: string,
}

I've heard a bit about apollo-federation but I can't put a graphql server in front of every api of our system, so I'm kind of lost on how I can achieve transparency for my front app when data are coming from two different sources.

If anyone has already encounter the same problem or have advice on something I can do, I'm all hear :)

Upvotes: 0

Views: 589

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84687

You need to decide what "shape" of the User type makes sense for your client app, regardless of what's being returned by the REST APIs. For this example, let's say we go with:

type User {
  id: String
  mail: String
}

Additionally, for the sake of this example, let's assume we have a getUser field that returns a single user. Any arguments are irrelevant to the scenario, so I'm omitting them here.

type Query {
  getUser: User
}

Assuming I don't know which API to query for the user, our resolver for getUser might look something like this:

async () => {
  const [userFromA, userFromB] = await Promise.all([
    fetchUserFromA(),
    fetchUserFromB(),
  ])

  // transform response
  if (userFromA) {
    const { id, communication: { mail } } = userFromA
    return {
      id,
      mail,
    }
  }

  // response from B is already in the correct "shape", so just return it
  if (userFromB) {
    return userFromB
  }
}

Alternatively, we can utilize individual field resolvers to achieve the same effect. For example:

const resolvers = {
  Query: {
    getUser: async () => {
      const [userFromA, userFromB] = await Promise.all([
        fetchUserFromA(),
        fetchUserFromB(),
      ])
      return userFromA || userFromB
    },
  },
  User: {
    mail: (user) => {
      if (user.communication) {
        return user.communication.mail
      }
      return user.mail
    }
  }, 
}

Note that you don't have to match your schema to either response from your existing REST endpoints. For example, maybe you'd like to return a User like this:

type User {
  id: String
  details: UserDetails
}

type UserDetails {
  email: String
}

In this case, you'd just transform the response from either API to fit your schema.

Upvotes: 1

Related Questions