← 글 목록

notes

sql-GraphQL

SQL과 GraphQL의 데이터 조회 방식·스키마·API 설계 차이 비교.

1. GraphQL이란?

GraphQL은 API를 위한 쿼리 언어이자, 런타임으로 쿼리를 처리하기 위한 서버사이드 런타임입니다. Facebook에서 개발했으며, 2015년에 오픈소스로 공개되었습니다.

1.1 주요 특징

  • 선언적 데이터 페칭: 필요한 데이터만 정확히 요청
  • 단일 엔드포인트: 모든 요청이 하나의 엔드포인트로 처리
  • 강력한 타입 시스템: 스키마 기반의 타입 안전성
  • 실시간 데이터: Subscription을 통한 실시간 업데이트
  • 자동 문서화: 스키마 기반의 자동 문서 생성

1.2 REST API와의 비교

2. GraphQL 기본 개념

2.1 스키마 (Schema)

GraphQL 스키마는 API의 타입 시스템을 정의합니다.

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

2.2 타입 시스템

  • 스칼라 타입: Int, Float, String, Boolean, ID
  • 객체 타입: 사용자 정의 타입
  • 리스트 타입: [Type]
  • NonNull 타입: Type!
  • 인터페이스: 공통 필드 정의
  • 유니온 타입: 여러 타입 중 하나
  • 열거형: enum

2.3 쿼리 (Query)

데이터를 조회하는 작업입니다.

query GetUser {
  user(id: "1") {
    name
    email
    posts {
      title
      content
    }
  }
}

2.4 뮤테이션 (Mutation)

데이터를 변경하는 작업입니다.

mutation CreatePost {
  createPost(input: {
    title: "새 포스트"
    content: "내용"
  }) {
    id
    title
    content
  }
}

2.5 서브스크립션 (Subscription)

실시간 데이터 업데이트를 위한 기능입니다.

subscription OnPostCreated {
  postCreated {
    id
    title
    content
  }
}

3. GraphQL 스키마 디자인

3.1 타입 정의

# 스칼라 타입 정의
scalar DateTime
scalar Upload

# 인터페이스 정의
interface Node {
  id: ID!
}

# 유니온 타입 정의
union SearchResult = User | Post | Comment

# 열거형 정의
enum UserRole {
  ADMIN
  USER
  GUEST
}

# 객체 타입 정의
type User implements Node {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
  createdAt: DateTime!
  updatedAt: DateTime!
}

3.2 입력 타입

input CreateUserInput {
  name: String!
  email: String!
  password: String!
  role: UserRole = USER
}

3.3 인자와 변수

query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
  }
}

4. GraphQL 클라이언트

4.1 Apollo Client

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: '<http://localhost:4000/graphql>',
  cache: new InMemoryCache()
});

4.2 쿼리 실행

import { gql, useQuery } from '@apollo/client';

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
    }
  }
`;

function UserProfile({ userId }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId }
  });

  if (loading) return <Loading />;
  if (error) return <Error error={error} />;

  return <div>{data.user.name}</div>;
}

4.3 뮤테이션 실행

import { gql, useMutation } from '@apollo/client';

const CREATE_POST = gql`
  mutation CreatePost($input: CreatePostInput!) {
    createPost(input: $input) {
      id
      title
      content
    }
  }
`;

function CreatePostForm() {
  const [createPost, { loading, error }] = useMutation(CREATE_POST);

  const handleSubmit = async (input) => {
    try {
      const { data } = await createPost({
        variables: { input }
      });
      // 성공 처리
    } catch (error) {
      // 에러 처리
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

5. GraphQL 서버

5.1 서버 구현

import { ApolloServer } from 'apollo-server';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({
    // 컨텍스트 설정
    auth: req.headers.authorization
  })
});

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

5.2 리졸버 구현

const resolvers = {
  Query: {
    user: (parent, args, context) => {
      return context.db.users.find(user => user.id === args.id);
    }
  },
  Mutation: {
    createUser: (parent, args, context) => {
      const user = context.db.users.create(args.input);
      return user;
    }
  },
  Subscription: {
    userCreated: {
      subscribe: (parent, args, context) => {
        return context.pubsub.asyncIterator(['USER_CREATED']);
      }
    }
  }
};

6. GraphQL 모범 사례

6.1 스키마 설계

  • 명확하고 일관된 네이밍 컨벤션 사용
  • 복잡한 타입은 인터페이스로 추상화
  • Nullable 필드 신중하게 사용
  • 문서화 주석 활용

6.2 성능 최적화

  • 데이터 로더 사용
  • 페이지네이션 구현
  • 캐싱 전략 수립
  • 쿼리 복잡도 제한

6.3 보안

  • 인증/인가 구현
  • 쿼리 깊이 제한
  • 비용 제한
  • 입력값 검증

6.4 에러 처리

const resolvers = {
  Query: {
    user: async (parent, args, context) => {
      try {
        const user = await context.db.users.find(args.id);
        if (!user) {
          throw new UserNotFoundError();
        }
        return user;
      } catch (error) {
        throw new ApolloError(
          '사용자를 찾을 수 없습니다.',
          'USER_NOT_FOUND',
          { userId: args.id }
        );
      }
    }
  }
};

7. GraphQL 도구

7.1 개발 도구

  • GraphiQL: 대화형 GraphQL IDE
  • Apollo Studio: API 관리 및 모니터링
  • GraphQL Code Generator: 타입 생성
  • GraphQL Playground: API 테스트

7.2 테스트 도구

import { createTestClient } from 'apollo-server-testing';

const { query, mutate } = createTestClient(server);

test('사용자 조회', async () => {
  const res = await query({
    query: gql`
      query GetUser($id: ID!) {
        user(id: $id) {
          name
        }
      }
    `,
    variables: { id: '1' }
  });

  expect(res.data.user.name).toBe('John Doe');
});

8. GraphQL 배포 및 운영

8.1 모니터링

  • 쿼리 성능 추적
  • 에러 모니터링
  • 사용량 분석
  • 스키마 변경 추적

8.2 버전 관리

  • 스키마 진화
  • 하위 호환성 유지
  • 점진적 롤아웃
  • 클라이언트 마이그레이션

9. 결론

GraphQL은 현대적인 API 개발에 많은 이점을 제공합니다:

  • 개발자 경험
  • 타입 안전성
  • 자동 문서화
  • 강력한 도구 지원
  • 성능
  • 필요한 데이터만 요청
  • 효율적인 캐싱
  • 실시간 업데이트
  • 유지보수성
  • 단일 진실 공급원
  • 명확한 스키마
  • 쉬운 버전 관리
  • 확장성
  • 마이크로서비스 통합
  • 다양한 데이터 소스
  • 커스텀 확장

GraphQL을 도입할 때는 팀의 상황과 프로젝트 요구사항을 고려하여 점진적으로 적용하는 것이 좋습니다.