본문 바로가기

Web.d

[GraphQL] How to use GraphQL Properly

반응형

목차

1️⃣ GraphQL이란?
  - GraphQL,
  - vs REST
2️⃣ GraphQL 주요 문법
  - ...
3️⃣ GraphQL을 제대로 쓰기 위해
  - Fragment Driven Development
  - Data Masking

 


1️⃣ GraphQL이란?

- GraphQL,

GraphQL은 하나의 쿼리 언어이다.

웹 클라이언트가 데이터를 서버로부터 효율적으로 가져오기 위한 목적을 가지고 페이스북에서 고안하였다.

API의 데이터를 알기 쉽게 문서로써 제공하고, 클라이언트에서 필요한 것을 정확히 요청할 수 있는 권한을 제공하며, API를 더 쉽게 확장하고 유지관리 할 수 있게끔 제공한다.

이를 통해, FE 개발자는 nested한 연관된 데이터들을 한 번에 요청이 가능해 코드적으로나 네트워크적으로나 큰 이점을 가질 수 있게 된다.

 

기존에 REST는 자원(주소(url)를 통해 리소스를 식별함)과 행위(HTTP Method)를 규제한다. 이 “자원”을 통해 데이터를 주고받는다.

GraphQL은 이와 달리, “자원” 자체가 주고받는 데이터를 가리키지 않는다. 또한 HTTP Method 역시 의미를 가지지 않는다. method를 특정하고, 단일 엔드 포인트를 사용함으로써 최대한 사용하기 편하게끔 되어있다.

비교하여 더 알아보자.

 

- vs REST

REST와 비교하여 GraphQL은 다른 언어일 뿐이지, 완전히 HTTP의 다른 패러다임을 가져오는 것은 아니다. HTTP 패킷을 살펴보면, Header method로 POST, Body에 요청하는 쿼리들을 담아 처리하기 때문에, 완전히 별개인 개념은 아닌 것이다. 똑같이 Request를 보내고 Response를 응답받는다. 이로 인해 기존의 API 관리 도구나, 구조를 고려할 필요가 없다는 장점이 있다.

 

REST API와 GraphQL API의 사용

 

GraphQL은 API 명세서가 아니다. 기존 데이터로 Query를 수행하는 런타임일 뿐이다. 즉, 백엔드에서 데이터에 대한 Schema를 설명하는 type system을 제공하면, 클라이언트에 의해 어떤 자원, 어떤 데이터를 언제 요청하는지 결정된다.

이를 통해, GraphQL은 쿼리에 제시된 데이터만 받고, REST는 특정 자원의 모든 데이터를 받는다. 성능 이슈와 불필요한 네트워크 오버헤드를 방지할 수 있다. 성능으로도 이점을 가진다.

또한, GraphQL은 스키마를 기반으로 클라이언트가 필요한 데이터 구조를 요청하고 반환받기 때문에, 서버 엔드포인트 구조에 맞춰 데이터를 요청하고 파싱 해야 할 필요가 없다. 즉, 서버와의 의존도를 낮추어 보다 더 유연하게 사용이 가능하다.

 

그러나, 단일 엔드 포인트를 사용하면서 생기는 단점들도 대두된다.

보안적인 문제부터 시작하여, 한 쿼리에서의 복잡성으로 인한 서버의 리소스 과부하 문제, 캐싱 구현이 어려워 되려 REST보다 비효율적인 통신이 될 수 있다는 문제까지 다양하다. 연구결과에 다르면 요청이 3천 건을 초과할 경우 GraphQL의 성능이 REST보다 떨어진다는 연구결과가 있다.

 

그렇다면 어떤 것이 더 나은 메커니즘인가?

이러한 Trade-of 로써 “어떠한 기준으로 선택할 것인가”가 가장 중요할 것 같다.

REST가 자원을 효율적으로 주고받는 방식에 대한 고민이 주라면, GraphQL은 화면 구현을 더 잘하고자 하는 고민이 주이다. 서비스의 특성이 자원 통신에 더 집중해야 하는 경우(극단적으로는 UI를 그릴 필요가 없는 경우)에는 REST를 사용하는 것이 더 옳은 선택이 될 수 있다. 이와 반대로 화면에 대한 비지니스 도메인을 식별하기 어려워 REST로써 관리하기에 한계가 느껴지고, 빠르고 지속적인 인터페이스 변경으로 유지관리에 어려움이 있다면 GraphQL가 적절하다.

결론적으로 만들고자 하는 서비스가 어떤 서비스인지, 복잡도는 어느 정도인지, 기술 수준은 어떠한지 등 다양한 부분을 고민하고 의사결정하여 REST / GraphQL을 결정하여 나아갈 문제이다.

 

이제 사용 단계로 넘어가 보자.

 

2️⃣ GraphQL 주요 문법

전체 생략합니다.

구체적인 사용법은 🔗 공식문서를 참고하면 좋을 것 같습니다.

 

- 사용에 있어 GraphQL의 주의할 점은?

데이터 요청이 클라이언트 입장에서 굉장히 유연하다.

클라이언트에서 필요한 것을 정확히 요청할 수 있는 권한을 제공하며,

이 말은 즉슨, 필요 이상의 데이터들도 요청할 수 있다는 것이다.

작은 컴포넌트의 구현을 위해 Query 타입의 모든 데이터를 요청할 수도 있는 유연성을 가진다. API 명세가 정해져 있지 않기 때문에, UI에 기반하지 않는 쿼리 작성은 과도한 데이터 요청과 서버 부하를 높인다.

이를 방지하기 위해 아래에서 다룰 Fragment Driven 에 맞게 개발함이 필요하다.

 

스키마에 대한 Rule?

오랜 기간 정착되어 온 REST와 달리, 스키마에 대한 Rule이 강하지 않다.

따라서 쿼리를 작성하는 자유도가 높기 때문에, 스키마 작성을 위한 FE/BE 간의 새로운 협업 방식이 필요할 수 있다.

개발에 있어 적절한 쿼리를 사용하기 위해서는 비지니스 도메인에 대한 E-R 식별이 정확하게 되어야 하고, 이를 기반으로 클라이언트를 위한 선언적인 스키마 작성이 요구된다.

 

3️⃣ GraphQL을 제대로 쓰기 위해 

- Fragment Driven Development

React의 각 UI 컴포넌트는 컴포넌트에 알맞는 데이터와 핸들링 함수들을 가지고 있다.

이 데이터들에 대해 더도 말고 덜도 말고 딱 알맞게 통신이 가능하다면, GraphQL의 장점을 온전히 살릴 수 있다. 여러 엔드포인트를 고려할 필요도 없고, over-fetching, under-fetching 문제가 해결될 수 있다.

이때 컴포넌트만의 데이터, 쿼리 데이터로, “Fragment”가 제 역할을 다 해줄 수 있다.

개별 컴포넌트가 필요한 fragment만을 props으로 넘겨받는 형식이 아닌 부모 컴포넌트가 불러온 데이터를 자식 컴포넌트들이 공유하기 시작하면 컴포넌트 간 의존성이 발생하게 된다. 이때 기획의 변동 등의 이유로 어떤 컴포넌트에서 특정 필드를 사용하지 않게 되어 삭제를 하게 되면, 그 필드를 사용하는 다른 컴포넌트에 버그가 발생할 수 있다.

컴포넌트와 컴포넌트 데이터만의 Fragment를 정의하여 사용한다면, 서로가 강한 의존성을 가지며 한 컴포넌트, 한 모듈로써 온전한 동작을 기대할 수 있다.

 

다음 코드 예시를 살펴보자.

with fragment
// 자식 컴포넌트

const USER_FEILDS_FOR_USER_PROFILE = gql`
  fragment userFieldsForUserProfile on User {
    id
    name
    address
    email
  }
`;

const UserProfile = ({post}) => (
  <div>
    <div>
      {post.name}
    </div>
    <div>
      {post.address}
    </div>
    <div>
      {post.email}
    </div>
  </div>
);

export { userFieldsForUserProfile };
export default UserProfile;
// 부모에서의 패칭을 위한 쿼리

const USERS = gql`
  query usersForUserProfile {
    users() {
      id
      ...userFieldsForUserProfile
    }
  }
  ${USER_FEILDS_FOR_USER_PROFILE}
`;

 

without fragment
// 자식 컴포넌트

const UserProfile = ({post}) => (
  <div>
    <div>
      {post.name}
    </div>
    <div>
      {post.address}
    </div>
    <div>
      {post.email}
    </div>
  </div>
);

export default UserProfile;
// 부모에서의 패칭을 위한 쿼리

const USERS = gql`
  query usersForUserProfile {
    users() {
      id
      name
      address
      email
    }
  }
`;

 

fragment 로써 모듈화를 명시하지 않는다면,

어느 자식 컴포넌트를 위한 쿼리 식별인지 명확하지 않다.

위 코드 예시와 같이 “address” 속성의 UI가 수정되어 삭제된다면, 부모 컴포넌트에서의 쿼리 정의로 인해 자연스레 over-fetching 문제를 직면하게 될 것이다.

 

이러한 문제는 대규모의 팀이 개발을 진행할 경우 즉시 드러나지 않을 수도 있다. 이것은 어떤 코드 리뷰나 자동화 테스트 등으로 극복할 수 없는 시스템적인 문제다.

팀에서 fragment-driven한 개발 문화가 합의되면, 향후 컴포넌트 간 의존성에 의한 버그 발생률을 크게 낮출 수 있고, 개발할 때 다른 컴포넌트와의 의존성을 고려하지 않아도 된다는 뜻이다.

 

- Data Masking

각 컴포넌트 간의 관심사를 분리하고 의존성을 제거함에 있다. 특히 부모-자식 컴포넌트 간의 의존성을 낮춰준다면 그 자체로 유지보수가 편리해지고 협업에 있어 생산성이 극대화될 수 있다. UI 변경에 따라 수정하는 범위가 급격하게 줄어든다.

이를 위해 Fragment를 이용한 “Data Masking”이 힘을 발휘한다.

말 그대로, 자식이 사용하는 fragment data에 대해 마스킹(캡슐화)을 하여, 부모가 이 데이터에 대해 알지 못하게끔 한다. 부모-자식 간의 관심사를 완전히 분리하고, 마스킹되었던 데이터는 자식으로 내려와 언마스킹을 하여 데이터를 사용하는 것이다.

위 <with Fragment> 코드 예시로 보면, UserProfile 컴포넌트에서 데이터를 prop으로 받는 것이 아닌, Masking 된 데이터 자체로 받아 컴포넌트 내에서 Unmasking 하여 UI 데이터로 사용하는 것이다.

컴포넌트마다 Fragment가 컴포넌트와의 강한 결합을 하여 존재하고, 이 Fragment를 부모 컴포넌트에서 사용한다. 이것이 이뤄진다면, 데이터 변경이 있을 때에 부모 컴포넌트와는 관계없이 해당 컴포넌트에서의 수정만이 보장되고 이는 곧 높은 생산성을 불러온다.

 


Reference

 

GraphQL #Value :Of the frontend, by the frontend, for the frontend - Tridge Tech Story

이번 포스팅에서 다루고자 하는 주제는 GraphQL의 존재 이유이자 존재 가치, 그리고 그 가치를 기반으로...

blog.naver.com

 

GraphQL #Fragment - Tridge Tech Story

React, Angular, Vue 등 Single Page Application(SPA)들이 새로운 프론트엔드 개발의 패러다임이...

blog.naver.com

 

반응형