본문 바로가기
프로젝트/shop-web(React)

Front-end) 프로젝트 GraphQL 연동과 커스텀 Hook

by 공부하는 프로그 2024. 10. 28.

이전 포스터

 

Front-end) 프로젝트 아토믹 디자인 패턴 적용

이전 포스트 Front-end) 프로젝트 설계 및 기초 세팅쇼핑몰 기능 구상1. 메인 화면HeaderLogo카테고리캐릭터만화게임개인커스텀문의간편 메뉴북마크찜장바구니로그인메인 화면 상품 목록진행중인

dmltn3426.tistory.com


✅ Intro

  • GraphQL을 간단하게 공부하고 이것의 장단점 대해 간략하게 설명해보자.
  • GraphQL을 적용하고 설계해 보자.
  • 커스텀 Hook useGraphQL 만들어 보자
  • GraphQL을 왜 쇼핑몰 프로젝트에 적용할 생각을 하였는지.

 

🔍 GrapQL이란 무엇인가?

기존의 Rest-API가 가지고 있던 한계점을 극복하기 위해 개발된 쿼리 언어이다.
  • Over-Fething 이슈 해결
    • REST API를 이용하면 백엔드(BE)가 URL의 각 리소스에 사용할 수있는 데이터를 정의하는 반면, 프론트엔드(FE)는 리소스의 일부만 필요하더라도 항상 리소스의 모든 정보를 요청해야 한다.
    • GraphQL을 사용하면 필요한 필드만 명시적으로 요청할 수 있기 때문에 해당 문제를 근본적으로 해결한다고 할 수 있다.
  • Under-Fetching 이슈 해결
    • 일반적으로 REST API에서는 특정 데이터를 사용하려면 여러 엔드포인트에 액세스하여 데이터를 받아와야 한다.
      • 첫번째 - /student/<id> 엔드 포인트
        두번째 - 학생의 정보를 가져오기 위해서 /student/<id>/info 엔드 포인트
        세번째 - 학생의 친구 목록을 가져오기 위한 /student/<id>/friends 엔드 포인트
    • GraphQL은 단일 쿼리에 구체적인 데이터 요구 사항을 적어 GraphQL 서버에 보내기만 하면 된다.
 

React) Apollo Client GraphQL 사용해보기 - 1

※ GraphQL을 들어가기 전에GraphQl이 무엇인지와 이의 필요성에 대해 공부해 보고, React와 Apollo Client를 함께 사용 하는 방법을 Hook기반의 케이스로 공부하고 실습해봤던 내용을 소개하려고 한다. 먼

dmltn3426.tistory.com

 

❓쇼핑몰 프로젝트에서 GraphQL를 써보았을 때 장점과 단점은 무엇이였는지?

아직 API 프로토콜 현황을 보면 아직까지도 REST를 가장 널리 사용하는 것을 확인 할 수 있는거 처럼 GraphQL을 프로젝트에 적용시키는 것은 처음이여서 적용시키는 과정에서 Back/Front End 둘 다 어려운 과정을 겪었다. 쇼핑몰 프로젝트에 GraphQL을 적용하면서 Front-End적으로 장점과 단점을 확인 할 수도 있었다.

 

장점

  1. Apollo client를 설정하면 하나의 EndPoint를 가지고 있어서 유지보수에 용이했었다.
  2. 쇼핑몰에서 카테고리를 원하면 쿼리문을 통해서 내가 원하는 Response Parameter를 지정하여 가져올 수 있었다.
  3. 원하는 데이터를 가져올 수 있어서 서버를 여러번 호출해서 리소스를 낭비 시키지 않아도 된다.
  4. 쇼핑몰 아이템 데이터를 가져올 때에는 데이터를 한번에 반환하는 방식으로 원하는 데이터를 최적화하기가 용이하다.

단점

  1. GraphQL은 하나의 URL로 처리하기에, HTTP에서 제공하는 캐싱 전략을 그대로 사용하는 것이 불가능해서 장점이자 단점이라고도 생각한다.
  2. 파일 업로드를 처리할 때 문제점이 발생하였다.

 

어려웠던 점

위에서 장점과 단점을 간단하게 프로젝트를 진행하면서 겪었던 일을 나열해 보았다. 그 중에서도 사용자의 시점에서 생각했을 때 리뷰 작성을 하게 된다면 리뷰 등록은 실시간으로 중간 저장이 필요하다고 생각을 하게 되었는데 그렇게 되면 GraphQL을 호출하여 삭제, (임시)등록 혹은 수정, (임시)등록을 고려해 볼 필요가 있다고 생각하게 되었고 이 부분에서 Back-End와도 많은 상의를 나눠보고 결론을 지어 이중 Mutation을 이용하여 동시에 호출하여 임시 저장버튼을 만드는것으로 합의하여 설계하였다. 이 부분에서는 아직 미숙한 부분이 보이기도 했지만 타협점을 찾고 잘 진행했다.

 

결론

이러듯이 GraphQL은 Front-End가 장점만 본다면 매우 매력적으로 보일 지 모르겠지만 사용자의 입장을 고려하여서 설계를 진행하게 되면 고려해야 될게 많아서 아직까지는 REST-API를 선호하는 것이 아닌가 싶다.

 

 

쇼핑몰 GraphQL과 Apollo Client 적용

  • api/gql 폴더 안에 mutation를 관리할 폴더인 mutations와 query를 관리할 폴더인 queries를 만들고 client.ts를 통해서 GraphQL API와 연결하는 http link 생성하고 http link와 InMemoryCache의 새 인스턴스를 전달하여 Apollo Client를 인스턴스화를 해주는 파일을 만들어주는 작업을 밑에 코드와 같이 해준다.
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';

// Apollo Client 인스턴스를 GraphQL API와 연결하는 http Link생성한다.
const httpLink = createHttpLink({
    uri: '{GraphQL 서버}',
});

// httpLink 및 InMemoryCache의 새 인스턴스를 전달하여 ApolloClient를 인스턴스화한다.
export const client = new ApolloClient({
    link: httpLink,
    cache: new InMemoryCache(),
});

 

  • src/index.tsx에 코드에서 기존에 App을 호출하는 부분에 ApolloProvider를 감싸주고 Apollo Client를 인스턴스한 client를 props로 설정해준다.
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
    <StyledEngineProvider injectFirst>
        <ApolloProvider client={client}>
            <CookiesProvider>
                <BrowserRouter>
                    <RecoilRoot>
                        <App />
                    </RecoilRoot>
                </BrowserRouter>
            </CookiesProvider>
        </ApolloProvider>
    </StyledEngineProvider>,
);


이렇게 설계 단계를 지난 후에 Apollo Client를 Front에 적용시키면서 느꼈던 점을 간단히 설명해 보자.

 

개발을 진행하면서 Apollo Client를 적용시키면서 느꼈던 점

※ Apollo Client를 공부하고 프로젝트에 직접 적용시키면서 Apollo Client Caching의 작동 원리를 공식문서와 함께 중점적으로 파악해 보았고 아래 내용을 보았다.

 

캐시를 Apollo Client에 보내면, Apollo Client가 쿼리 응답을 받을 때 마다, 자체 캐시 안에서 분리된 별개의 엔트리에 자동적으로 데이터를 저장한다.
처음으로 쿼리할 때 데이터를 가져오는 흐름은 다음과 같다.

동일한 객체에 대해 실행될 때마다 흐름은 다음과 같다.

출처 : Apollo Client 공식 문서

 

GraphQL Custom Hook으로 만들어 보자

이렇게 유지보수나 최적화적인면에서 GraphQL을 이용하는 것이 유리해 보여 사용했다. 하지만 GraphQL에서 useQuery나 useMutation과 같음 Hook을 이용해서 사용할 때 마다 Loading이나 Error처리를 계속해서 해줘야하는데 이것을 한번에 처리하여 재활용성을 높이도록하고 나중에 유지보수를 하더라도 파일 하나만 수정하여 유지보수의 효율성을 높이도록 하고 싶어서 Custom Hook인 useGraphQL.tsx을 만들었다.

 

import { useEffect } from 'react';
import { DocumentNode } from 'graphql/language';
import { LazyQueryHookOptions, MutationHookOptions, useLazyQuery, useMutation } from '@apollo/client';
import { useRecoilState } from 'recoil';
import loadingAtom from '@recoil/atoms/loadingAtom';

interface graphQLProps<T> {
    query: DocumentNode;
    request?: T;
    type: 'query' | 'mutation';
    option?: MutationHookOptions | LazyQueryHookOptions | T;
}

/*
 *
 * query는 GraphQL의 쿼리를 받는 props이다. 
 * request는 필터링 혹은 variables에 필요한 request 값을 받는 값을 담아주는 props이다.
 * type은 mutation을 호출하는지 query를 호출하는지 타입을 정해주기 위한 props이다.
 * option은 headers에 들어갈 데이터들의 option을 담는 props이다.
 */
const useGraphQL = <T,>({ query, request, type, option }: graphQLProps<T>) => {
    const [loadingGlobal, setLoading] = useRecoilState(loadingAtom);

    const selectType = () => {
        if (type === 'mutation') {
            return useMutation(query, {
                context: {
                    headers: { ...option },
                    fetchOptions: {
                        credentials: 'include',
                    },
                },
                variables: {
                    request: { ...request },
                },
            });
        }

        return useLazyQuery(query, {
            context: {
                headers: { ...option },
            },
            variables: {
                request: { ...request },
            },
        });
    };
    const [refetch, { data, loading, error }] = selectType();

    const modifyLoading = (state: boolean) => {
        if (state) setLoading(loadingGlobal + 1);
        else setLoading(loadingGlobal - 1 < 0 ? 0 : loadingGlobal - 1);
    };

    useEffect(() => {
        if (loading) modifyLoading(true);
        else modifyLoading(false);
    }, [loading]);

    useEffect(() => {
        if (error) alert(error.message);
    });

    return { data, refetch };
};

export default useGraphQL;

 

selectType을 통해서 값을 받으면 refetch와 data, loading, error이 갱신되고 loadingGlobal이라는 recoil을 통해서 전역으로 loading의 상태를 관리해준다. 그리고 useEffect를 통해서 loading값과 error 값을 변경한다.

 

※ 그렇다면 recoil을 통해 loading을 전역으로 관리하기만 하고 화면을 나타내는 부분은 어떻게 하면 좋을까?
그 부분은 Loading페이지를 만들어서 전역으로 관리하는 loading 상태 값을 통해 Layout 페이지에 적용을 시켜서 loading 중이면 화면을 나타내주면 해결된다.

 

✅결론

  • GraphQL은 하나의 URL로 처리하기에 유지보수 하기에는 용이하지만, HTTP에서 제공하는 캐싱 전략을 그대로 사용하는 것이 불가능해서 장점이자 단점을 가지고 있기도 하다.
  • 아직 REST처럼 대중적이지 않고 설계 단계에서 어려움이 있을 수 있다.
  • 데이터 리소스 낭비가 적다.
  • 프로젝트에서 useGraphQL이라는 커스텀 Hook을 만들어서 유지보수에 더욱 편의성을 높였다. 

 

참고

출처: Apollo Client 공식문서

 

Caching in Apollo Client

Search Apollo content (Cmd+K or /)

www.apollographql.com

 

출처: Api 프로토콜 빈도 현황

 

2023년 API 프로토콜 현황 | GeekNews

Postman이 4만명의 개발자 대상 조사를 통해 정리한 API 프로토콜 트렌드와 장/단점REST, WebHooks, GraphQL, SOAP, WebSocket, gRPC 등REST아직 가장 널리 사용. 지난 2년간 92% 에서 86%로 감소단순성, 확장성 및 웹

news.hada.io