GraphQL - 2

doodream·2022년 2월 7일
0

GraphQL

목록 보기
3/5
post-thumbnail

데이터 타입끼리 연동하기

이전시간에 UserType 이라는 데이터 스키마를 만들었습니다. 하지만 예시로 들었던 사용자 모델하고는 조금 다른 부분이 있었습니다.


위 사진을 보게 되면 사용자 모델에서는 companyId 라는 부분으로 회사를 구분지려고 했습니다만 실제 UserType에서는 해당부분은 없고 새로운 타입인 CompanyType이 등장합니다.
이전 블로깅에서 봤다시피 GraphQL은 resolve라는 것으로 쿼리를 분석하고 요청 받은 데이터를 가공해서 반환합니다. 이부분에서 JSON 타입을 가공해서 UserModel이 원하는 것으로 바꿔줘야합니다.

실제로 그래프 큐엘에서 어떻게 데이터가 처리되는 지 살펴봅시다.
schema.js

const graphql = require("graphql");
const axios = require("axios");
const { GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLSchema } = graphql;

const CompanyType = new GraphQLObjectType({
  name: "Company",
  fields: {
    id: { type: GraphQLString },
    name: { type: GraphQLString },
    description: { type: GraphQLString },
  },
});

const UserType = new GraphQLObjectType({
  name: "User",
  fields: {
    id: {
      type: GraphQLString,
    },
    firstName: { type: GraphQLString },
    age: { type: GraphQLInt },
    company: {
      type: CompanyType,
      resolve(parentValue, args) {
        const data = axios
          .get(`http://localhost:3000/companies/${parentValue.companyId}`)
          .then((res) => res.data);
        return data;
      },
    },
  },
});

const RootQuery = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    user: {
      type: UserType,
      args: { id: { type: GraphQLString } },
      resolve(parentValue, args) {
        const data = axios
          .get(`http://localhost:3000/users/${args.id}`)
          .then((res) => res.data);
        return data;
      },
    },
  },
});

module.exports = new GraphQLSchema({ query: RootQuery });
// 쿼리를 루트 쿼리로 받아서 그래프 큐엘 스키마 객체로 반환합니다.

CompanyType을 새로 만들었습니다. resolve 부분을 보면 인수가 두가지 있습니다. parentValue, args 이 두개는 각각 어떤 값을 가지는 것일까요?
RootQuery는 가장 처음 쿼리를 받아들이는 데이터 타입입니다. 우리가 보내는 쿼리를 보며 비교해봅시다.

query

{
  user(id : "40"){
    id,firstName
    company{
      id
    }
  }
}

가장 처음 user라는 속성에 접근하기를 원합니다. 루트 쿼리에서 user라는 타입에 args 속성을 정의해놓았습니다. 따라서 args는 문자열 타입인것을 알수 있고, resolve에서 해당 user 타입에 접근하기 위해서 DB에 접근하여 args.id 로 쿼리에서 원하는 UserType인 데이터 속성을 가져옵니다.

이후 쿼리문 아랫단에 company 라는 데이터 속성에서 id 속성을 접근하고자 합니다. 스키마의 UserType에서는 company 부분을 Company 타입으로 정의해 놓았습니다. 따라서 또다시 resolve에서 데이터 베이스에 다시 접근해서 해당 데이터를 가져오고자 합니다. 이때 parentValue 값은 현재 쿼리문(company)의 부모에 해당하는 user 의 resolve에서 가져온 UserType 데이터입니다. 즉, parentValue는 아래와 같은 값을 갖고 있습니다.

{ id: '40', firstName: 'hobo', age: 42, companyId: '2' }

위 값을 바탕으로 resolve에서는 companyId 값으로 다시 데이터베이스에 접근해서 Company에서 companyId값이 2인 데이터를 찾아 반환합니다. 이렇게 반환된 데이터들은 한번에 JSON형태로 반환되어

{
  "data": {
    "user": {
      "id": "23",
      "firstName": "doodream",
      "company": {
        "id": "1"
      }
    }
  }
}

라고 반환됩니다. 실제 데이터들이 부모관계로서 이어져 있는것이 아닙니다. 그저 데이터베이스에 필요할때마다 각각 접근해서 데이터를 조합해서 반환하는 것입니다.
데이터의 흐름만을 나타내자면 아래와 같습니다만 절대 데이터베이스가 부모관계에 있다는 것은 아닙니다. 데이터의 흐름만을 볼때 본질적으로 단방향 입니다.

이렇기 때문에 각각의 데이터 타입이 노드라고 생각되면 companyId 같은것이 edge로서 그래프를 그리게 되고 이러한 그래프를 바탕으로 쿼리문으로서 데이터를 요청하게 되니 이름대로 Graph Query Language 입니다.

RootQuery 다양화하기

const RootQuery = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    user: {
      type: UserType,
      args: { id: { type: GraphQLString } },
      resolve(parentValue, args) {
        const data = axios
          .get(`http://localhost:3000/users/${args.id}`)
          .then((res) => res.data);
        return data;
      },
    },
    company: {
      type: CompanyType,
      args: { id: { type: GraphQLString } },
      resolve(parentValue, args) {
        const data = axios
          .get(`http://localhost:3000/companies/${args.id}`)
          .then((res) => res.data);
        return data;
      },
    },
  },
});

위 코드를 보면 그냥 fields 속성에 형제 형태로 새로운 데이터 타입을 넣은 것을 확인 할수 있습니다.

그렇다면 실제 Company 타입에서 User 타입을 불러올수 있을까요? 데이터 타입끼리 서로 edge를 걸게 하려면 어떻게 해야할까요? 여기엔 순환참조 에러가 있을수 있으나 클로저를 이용해서 이를 해결합니다.

const UserType = new GraphQLObjectType({
  name: "User",
  fields: () => ({
    id: {
      type: GraphQLString,
    },
    firstName: { type: GraphQLString },
    age: { type: GraphQLInt },
    company: {
      type: CompanyType,
      resolve(parentValue, args) {
        const data = axios
          .get(`http://localhost:3000/companies/${parentValue.companyId}`)
          .then((res) => res.data);
        return data;
      },
    },
  }),
});

const CompanyType = new GraphQLObjectType({
  name: "Company",
  fields: () => ({
    id: { type: GraphQLString },
    name: { type: GraphQLString },
    description: { type: GraphQLString },
    users: {
      type: new GraphQLList(UserType),
      resolve(parentValue, args) {
        const data = axios
          .get(`http://localhost:3000/companies/${parentValue.id}/users`)
          .then((res) => res.data);
        return data;
      },
    },
  }),
});

데이터 타입이 서로 fields 속성에서 참조를 하고 있으나 함수로 값을 반환하기에 정의하는 시점에서는 서로 참조 하지 않음으로서 순환참조를 피해갔습니다.

쿼리를 날려보면 company에서도 user 데이터를 불러오는 것을 확인할 수 있습니다.

❗️ user에서도 company를 불러올 수 있는데, 계속 뎁스를 높여갈 수 있는거 아니에요?

맞습니다.

놀랍게도 그렇게 됩니다. 하지만 이러한 구조는 graphQL에서는 매우 드물뿐만 아니라 데이터와의 관계를 다르게 설정하므로서 충분히 피해 갈수 있습니다.

profile
일상을 기록하는 삶을 사는 개발자 ✒️ #front_end 💻

0개의 댓글