smob_logo
smob_logo
header-image
GRAPHQL법인회원권 예약 시스템의 상태 관리Apollo Client와 GraphQL 정규화를 활용한 효율적 캐싱 전략2023.08.07

GraphQL 정규화 방식

GraphQL 쿼리는 명확한 타입을 기반으로 하며, 각 필드는 고유의 타입을 가지고 있습니다. 클라이언트가 요청하는 데이터는 서버의 스키마에 맞추어 자동으로 정규화됩니다. 이를 통해 클라이언트가 응답 데이터를 캐싱하고, 같은 데이터를 여러 곳에서 참조할 때 효율적으로 관리할 수 있습니다.

아래 예시는 법인회원권 예약 시스템에서의 데이터를 GraphQL로 요청하고 Apollo Client를 사용해 캐싱하는 방법입니다.

법인회원권 예약 시스템

스크린샷 2024-09-09 10.12.24.png

법인회원권 예약 현황 Graphql Query

query 법인회원권_예약현황_Query {
	예약슬롯_리스트 {
		id         # 예약 슬롯 ID
		날짜
		최대_예약_매수
		예약_티켓_리스트 {
			id       # 예약 티켓 ID
			이름
			매수
		}
	}
}

이 쿼리는 예약 슬롯과 슬롯 안에 있는 예약 티켓 리스트를 함께 조회합니다. 각 슬롯은 고유한 id를 가지고 있으며, 티켓도 각각 고유의 id를 가집니다. Apollo Client는 이 id 값을 통해 데이터를 정규화하고, 이를 효율적으로 캐싱합니다.

GraphQL 데이터 Fetch 결과값

{
	"data": {
		"예약슬롯_리스트": [
			{
        "id": "1",
        "날짜": "2023-07-31T15:00:00.000Z",
        "최대_예약_매수": 10,
        ,
        "예약_티켓_리스트": [],
        "__typename": "예약슬롯"
      },
      ...
      {
	      "id": "1",
        "날짜": "2023-08-06T15:00:00.000Z",
        "최대_예약_매수": 10,
        ,
        "예약_티켓_리스트": [
	        {
            "id": "64d03b9be37267ea3dd3865e",
            "이름": "정규재",
            "매수": 1,
            "__typename": "예약_티켓"
          },
          ...
        ],
        "__typename": "예약슬롯"
      }
      ...
		]
	}
}

이 결과에서는 예약 슬롯과 그 안의 티켓 리스트가 함께 반환됩니다. 각 슬롯과 티켓은 고유한 id__typename을 통해 구분되며, Apollo Client는 이를 바탕으로 데이터를 자동으로 정규화하고 캐시에 저장합니다.

Apollo Client Cache에 저장된 값

{
	"예약_슬롯:1": {
    "id": "1",
    "날짜": "2023-07-31T15:00:00.000Z",
    "최대_예약_매수": 10,
    "예약_티켓_리스트": [],
    "__typename": "예약슬롯"
  },
	"예약_슬롯:2": {
    "id": "2",
    "날짜": "2023-08-06T15:00:00.000Z",
    "최대_예약_매수": 10,
    "예약_티켓_리스트": [
      { "__ref":"예약_티켓:1" },
    ],
    "__typename": "예약슬롯"
  },
	"예약_티켓:1": {
    "id": "64d03b9be37267ea3dd3865e",
    "이름": "정규재",
    "매수": 1,
    "__typename": "예약_티켓"
  }
  ...
}

Apollo Client는 각각의 예약슬롯예약티켓id를 기준으로 정규화하고, 이를 캐시에 저장합니다. 이 캐시는 고유의 id__typename을 통해 다른 쿼리나 변경 요청에서 동일 데이터를 참조할 수 있게 합니다.

Apollo Cache로 데이터/뷰 일관성 달성하기

GraphQL과 Apollo Client를 활용하여 데이터 정규화된 구조로 관리하면, 데이터 일관성을 유지하기가 훨씬 쉬워집니다. 특히 예약 시스템에서 새로운 예약을 등록한 후, 캐시를 통해 데이터를 직접 수정하면 추가적인 서버 호출 없이도 클라이언트 측의 상태를 업데이트할 수 있습니다.

예약 등록과 데이터 업데이트 하기

아래 예시는 예약 등록 시 발생하는 Mutation과 Apollo Cache를 이용해 데이터를 즉시 업데이트하는 방법을 보여줍니다.

const CREATE_MUTATION = gql`
  mutation createTicket(...) {
    ...예약_조회_티켓_Fragment
  }
`;

const 예약등록_컴포넌트 = () => {
  const [createTicket] = useMutation(CREATE_MUTATION);

  const onSubmit = () => {
    createTicket({
      variables,
      update: (cache, { data }) => {
        // 예약 티켓 Apollo Cache에 Fragment 기록
        const 새로운_예약티켓_Cache_참조값 = cache.writeFragment({
          id: cache.identify(data.예약_티켓_아이디),
          fragment: 예약_조회_티켓_Fragment,
          data: data,
        });

        // Cache된 예약 슬롯의 Data 업데이트
        cache.modify({
          id: cache.identify(data.예약_슬롯_아이디),
          fields: {
            예약_티켓_리스트(existingTickets = []) {
              return [...existingTickets, 새로운_예약티켓_Cache_참조값];
            },
            예약_가능_매수(currentCount) {
              return currentCount - data.새로운예약티켓.매수;
            },
          },
        });
      },
    });
  };

  return (
    <form onSubmit={onSubmit}>
      {/* 예약 등록 폼 */}
      <button type='submit'>예약 등록하기</button>
    </form>
  );
};
  1. Mutation: 예약 등록 버튼을 클릭하면 createTicket이라는 GraphQL Mutation이 실행됩니다.
  2. Cache Update: Apollo Client의 update 함수 안에서, 캐시에 직접 writeFragmentcache.modify를 사용하여 데이터를 업데이트합니다.
    • writeFragment를 사용해 새로 등록된 예약 티켓 데이터를 캐시에 기록하고, 참조값을 반환받습니다.
    • cache.modify를 통해 기존 예약 슬롯에서 예약 티켓 목록에 새로 등록된 티켓을 추가하고, 예약 가능한 매수도 업데이트합니다.

Apollo Cache를 통한 업데이트의 이점

  1. 추가적인 데이터 패치없이 상태 업데이트: 서버에 추가적인 요청을 보내지 않고, 캐시에 직접 데이터를 기록함으로써 빠른 UI 업데이트가 가능합니다.
  2. 데이터 일관성 유지: 참조값을 사용하여 Cache에 기록된 데이터를 활용함으로써, 애플리케이션 전반에서 일관된 데이터를 유지할 수 있습니다. 즉, 여러 컴포넌트에서 동일한 데이터를 참조할 경우, 모든 컴포넌트가 즉시 업데이트됩니다.
#graphql
#react
#apollo-client
avatar
By. 정규재안녕하세요. 잘 부탁 드립니다.