우리가 GraphQL을 선택한 이유

profile
FE Developer - 루카스
May 20, 2023
GPT 요약
Thumbnail

안녕하세요.
덴티움 IT팀의 FE Developer 루카스입니다.

프로젝트 진행이 급물살을 타게 되고 개발에 박차를 가하면서 하루하루 바쁜 나날을 보내고 있습니다. 그와 동시에 효율적인 협업 프로세스에 대해서도 팀원들과 고민하는 시간이 많아지게 되었습니다.

이번 글에서는 IT팀에서 GraphQL을 도입하게 된 배경과 특징을 소개하고, 효과적인 협업을 위해 어떤 방식으로 활용하고 있는지 공유하고자 합니다.

GraphQL 도입 배경

server와 client는 API(Application Programming Interface)를 활용해 상호 간 정보를 교환합니다. 그리고 API는 구축 방식에 따라 RESTGraphQL로 구분할 수 있습니다.

Tip
REST의 기원부터 자세히 알고 싶으시다면 아래의 영상을 참고해주세요.
Tip

GraphQL은 페이스북에서 만든 쿼리 언어입니다.
: GraphQL에 대해 좀 더 자세히 알고 싶다면

여기

를 참고해주세요.

REST VS GraphQL

엔드포인트 관점

REST: 리소스에 따라 여러 엔드포인트가 존재

GraphQL: 모든 데이터 쿼리에 대해 단일 엔드포인트 사용

한 페이지에 여러 개의 리소스가 필요한 상황을 생각해 보겠습니다. 예를 들어 book, user, author와 같은 리소스를 각각 다른 엔드포인트에 요청하게 되면 각 요청에 따른 응답을 받게 됩니다. 더 많은 리소스가 있다면, 더 많은 요청을 해야 하고 이는 응답시간이 늘어날 수 있음을 의미합니다.

Warning

비동기로 서버간 통신을 하더라도, 각각의 요청에 대한 응답을 받아야 하기 때문에 응답시간이 늘어날 수 밖에 없습니다.

const [book, user, author] = await Promise.all([
  fetch('host/book/1'),
  fetch('host/user/5'),
  fetch('host/author/90'),

  // ... 더 많은 리소스를 요청해야 할 수도 있다.
]);

반대로 GraphQL은 한 번의 요청과 응답으로 필요한 모든 리소스를 얻을 수 있습니다. 즉, 단일 요청으로 네트워크 응답시간을 줄일 수 있게 됩니다.

query GetData {
  book(id: "bookId") {
    title
    author {
      id
      name
    }
  }
  author(id: "authorId") {
    name
    books {
      id
      title
    }
  }
  user(id: "userId") {
    name
  }
}

query GetData 보시면 book, author, user를 모두 포함하고 있습니다. 이렇게 하나의 엔드포인트로 필요한 모든 리소스를 명시하고 요청하면 필요한 데이터만 응답받을 수 있습니다.

유연한 데이터 요청 관점

REST: 고정된 데이터 구조와 필드 제공

GraphQL: 클라이언트가 원하는 데이터 구조와 필드를 명시적으로 요청

기존 REST에서는 리소스에 대한 명세를 정의하여 데이터를 명세대로 보내줍니다.

{
	"patient": {
		"name": "루카스",
		"id": 5,
		"birthDt": "20101010",
		"age": 15,
		"isNewPatient": false
		...
	}
}

위 예시처럼 특정 환자에 대한 정보를 요청하면 명세에 정의한 대로 모든 데이터를 가져오게 됩니다. 프론트엔드에서는 모든 환자 정보를 알 필요가 없지만, 리소스의 데이터 구조를 정했기 때문에 모두 가져올 수밖에 없습니다. 필요하지 않은 정보까지 요청하는 것을 오버페칭(over-fetching)이라고 합니다.

이와 반대로, 어떤 페이지에서 환자 정보뿐만 아니라 보험 정보까지 가져와야 하는 요구사항이 생길 수 있습니다. 이런 경우 하나의 엔드포인트만으로는 필요한 정보를 충분히 얻을 수 없게 됩니다.

{
  // GET: example:3000/환자정보
	"patient": {
		"name": "루카스",
		"id": 5,
		"birthDt": "20101010",
		"age": 15,
		"isNewPatient": false,
		...
	},

  // GET: example:3000/보험정보
  "insurance": {
    "type": 1,
    "name": "건강보험",
    ...
  }
}

이러한 경우를 언더페칭(under-fetching)이라고 합니다. 보험정보를 가져오는 추가적인 엔드포인트가 필요하게 됩니다.

GraphQL에서는 필요한 데이터만 프론트엔드에서 선택적으로 요청할 수 있습니다. 그렇기 때문에 오버페칭이나 언더페칭이 일어나지 않고 효율적인 데이터 교환이 이루어질 수 있습니다.

# 필요한 데이터만 가져오기
query Get_Patient {
  patient {
    name
    age
  }
  insurance {
    type
    name
  }
}
Tip

IT팀은 GraphQL을 편리하게 사용하기 위해 Apollo를 사용하고 있습니다. Apollo는 GraphQL을 사용하는데 필요한 다양한 기능을 제공합니다.
: Apollo에 대해 좀 더 자세히 알고 싶다면 여기 를 참고해주세요.

Tip

Apollo 이외에는 Relay를 사용할 수 있습니다. Relay는 페이스북에서 만든 GraphQL 클라이언트 라이브러리입니다.
: Relay에 대해 좀 더 자세히 알고 싶다면 여기 를 참고해주세요.

커뮤니케이션 관점

REST: API 문서를 통해 논의. 변경사항에 대한 지속적인 커뮤니케이션 필요

GraphQL: 스키마를 통한 논의. 쿼리, 스키마, 코드 생성에 대한 풍부한 도구 지원으로 커뮤니케이션 비용 감소

REST 방식에서 프론트엔드는 백엔드 개발자가 작성한 API 명세를 기준으로 애플리케이션을 작성합니다.(ex - Swagger) 그리고 변경 사항이 발생할 때마다 변경된 사항을 알리고 문서를 업데이트 한 후 개발을 진행합니다. 하지만 이런 방식은 변경 사항에 대한 많은 커뮤니케이션 비용이 발생합니다. 또한, API 명세에 의존하기 때문에 프론트엔드의 개발은 백엔드 개발 진행 상태에 종속될 수 밖에 없습니다.

그에 반해 GraphQL은 스키마를 기준으로 백엔드와 프론트엔드가 독립적으로 개발을 진행할 수 있습니다. 변경 사항은 스키마에 즉각 반영되고, 이를 통해 변경 사항에 대한 커뮤니케이션 비용을 줄일 수 있습니다.

Thumbnail

또한 스키마를 기반으로 자동으로 API 문서화할 수 있기 때문에, 개발자들이 쉽게 API를 이해하고 사용할 수 있도록 도와줍니다.

프로젝트에 적합한 기술 = GraphQL

Warning

위의 비교는 GraphQL이 REST보다 장점이 될 수 있는 부분만 언급하였을 뿐, GraphQL이 REST보다 좋다는 것을 의미하지 않습니다. 프로젝트의 특성에 맞는 기술을 선택하는 것이 중요합니다.

GraphQL 활용이 필요한 이유를 정리해보면 다음과 같습니다.

  • 하나의 페이지에 여러 리소스가 필요한 경우가 있다.
  • 데이터 구조의 유연성이 필요한 프로젝트 특성이 있다.
  • 백엔드와 프론트엔드가 각각 독립적인 진행을 해야 하고 의존성을 줄여야 한다.

위 사항들을 종합해보면 GraphQL은 프로젝트의 특성에 맞는 기술이라고 할 수 있습니다.

Tip

REST로도 충분히 프로젝트를 진행할 수 있습니다. GraphQL을 사용하면 좋은 점이 많지만, 분명히 단점도 존재합니다.

GraphQL로 협업하기

스키마는 뷰(View) 기준으로 정하기

GraphQL을 활용한 협업은 스키마를 기준으로 진행됩니다. 그렇기 때문에 스키마 설계가 가장 중요합니다. IT팀은 어떤 기준으로 스키마 설계하는 것이 좋을지 고민한 끝에, 뷰를 기준으로 스키마를 작성하기로 하였습니다. GraphQL은 필요한 리소스만 요청할 수 있는 유연함이 있고, 필요한 리소스는 뷰에 의해 결정되기 때문에 스키마 정의에 기준으로 삼게 되었습니다.

API를 기다리지 않고 MSW(Mock Service Worker)로

프론트엔드와 백엔드는 스키마를 기준으로 독립적으로 개발을 진행할 수 있습니다. 독립적인 개발방식을 적극 활용하기 위해 선택한 방법이 MSW입니다. 다음은 MSW의 handler의 예시입니다.

const handler = [
  ...// 스키마
  graphql.query<Patient_FindAllQuery, Patient_FindAllQueryVariables>(
    'Patient_FindAll',
    (req, res, ctx) => {
      const variable = req.variables;
      const authenticatedUser = isAuthedUser();
      if (!authenticatedUser) {
        return res(
          ctx.errors([
            {
              message: 'Not authenticated',
              errorType: 'AuthenticationError',
            },
          ])
        );
      }

      // 데이터 보내기
      return res(
        ctx.data({
          Patient_FindAll: patientList,
        })
      );
    }
  ),
];

위와 같이 Handler를 등록해 두면, 실제 서버와 통신하는 것과 같은 환경에서 테스트를 진행할 수 있습니다. 이후에 백엔드 작업이 완료되고, 실제 서버와 통신하는 환경으로 옮기더라도 스키마 기준으로 작업을 진행했기 때문에 변경 사항이 발생하지 않습니다. 또한, 서버와 통신하는 부분을 hook처럼 재사용할 수 있는 로직으로 분리해 둔다면 컴포넌트에 영향을 거의 미치지 않고 변경할 수 있습니다.

편리한 라이브러리

GraphQL 진영에는 사용자의 편의성과 효율성을 높여주는 라이브러리가 많이 존재합니다. IT팀은 그 중 대표적인 Apollo를 사용하고 있습니다. Apollo의 Playground는 API 문서화를 자동으로 처리해주고, 클릭 한 번으로 필드를 추가 및 삭제할 수 있는 편의성을 제공합니다. 개인적으로 Swagger보다 다루기 편하다고 생각합니다.

Thumbnail

결론

GraphQL의 장점을 정리해보면 다음과 같습니다.

  1. 하나의 엔드포인트로 필요한 데이터를 모두 얻을 수 있다.
  2. 언더페칭, 오버페칭과 같은 낭비를 발생시키지 않는다.
  3. 커뮤니케이션 비용을 낮춰주고 개발 편의성을 개선시켜주는 관련 라이브러리가 많다.
  4. 백엔드와 프론트엔드가 독립적인 개발을 진행하기 쉽다.

아직 초기 단계이기 때문에 고민 사항들은 많지만, 위 장점들을 최대한 활용하여 효율적인 협업 프로세스를 구축하고자 합니다. 위의 내용이 도움이 되었으면 좋겠습니다.

긴 글 읽어주셔서 감사합니다.

profile
안녕하세요 👏
FE Developer 루카스입니다.