작성일 댓글 남기기

GraphQL의 고급 기능들(서브스크립션, 데이터 최적화, 인증 등)

1. 서브스크립션(Subscription)

서브스크립션실시간 데이터 업데이트를 처리하는 GraphQL의 기능입니다. 주로 실시간 채팅, 주식 가격 업데이트, 알림 시스템 등 실시간으로 데이터가 변동되는 애플리케이션에서 사용됩니다.

기본 개념

서브스크립션은 클라이언트가 서버와 실시간 연결을 유지하면서, 데이터가 변경될 때마다 자동으로 업데이트를 받을 수 있도록 하는 방식입니다. GraphQL에서는 이를 WebSocket과 같은 프로토콜을 통해 구현합니다.

예시

예를 들어, 새로운 메시지가 추가될 때마다 클라이언트가 실시간으로 업데이트를 받을 수 있도록 서브스크립션을 정의할 수 있습니다.

스키마 예시:

type Subscription {
messageAdded: Message
}

type Message {
id: ID!
content: String!
author: String!
}

서브스크립션 리졸버:

const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();

const resolvers = {
Subscription: {
messageAdded: {
subscribe: () => pubsub.asyncIterator(['MESSAGE_ADDED'])
}
},
Mutation: {
addMessage: (parent, { content, author }) => {
const message = { id: Date.now(), content, author };
pubsub.publish('MESSAGE_ADDED', { messageAdded: message });
return message;
}
}
};

이 예시에서 PubSub을 사용하여 새 메시지가 추가될 때마다 구독 중인 클라이언트에게 이를 실시간으로 전송할 수 있습니다. 클라이언트 측에서는 Apollo Client의 subscribe 메서드를 통해 실시간 데이터를 받을 수 있습니다.

클라이언트에서 서브스크립션 사용

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

const MESSAGE_ADDED = gql`
subscription {
messageAdded {
id
content
author
}
}
`;

function Messages() {
const { data, loading } = useSubscription(MESSAGE_ADDED);

if (loading) return <p>Loading...</p>;
return <p>New message: {data.messageAdded.content}</p>;
}

2. 데이터 최적화 (Batching, Caching, 그리고 DataLoader)

GraphQL에서는 데이터 전송을 효율화하고 최적화하는 여러 가지 방법이 있습니다. REST API와 마찬가지로, GraphQL에서도 데이터 최적화는 성능에 중요한 영향을 미칩니다.

1) Batching과 DataLoader

데이터를 효율적으로 가져오기 위한 도구로 DataLoader를 사용할 수 있습니다. DataLoader는 여러 개의 GraphQL 쿼리를 배치(batch) 처리하여 N+1 문제를 해결하는 데 유용합니다.

N+1 문제란, 데이터를 요청할 때 동일한 데이터베이스 쿼리가 여러 번 발생하는 성능 이슈를 말합니다. 예를 들어, 10명의 사용자를 가져올 때, 10번의 데이터베이스 쿼리가 실행될 수 있는데, DataLoader를 사용하면 이를 하나의 쿼리로 묶어 처리할 수 있습니다.

DataLoader 예시:

const DataLoader = require('dataloader');

const userLoader = new DataLoader(async (keys) => {
const users = await UserModel.find({ _id: { $in: keys } });
return keys.map(key => users.find(user => user.id === key));
});

const resolvers = {
Query: {
user: (parent, { id }, context) => {
return context.userLoader.load(id); // DataLoader 사용
}
}
};

효과:

  • 여러 번의 데이터베이스 쿼리를 하나로 묶어 처리해 성능을 최적화.
  • 요청당 쿼리의 수를 줄여 서버의 부하 감소.

2) 캐싱(Caching)

GraphQL에서는 데이터 요청 시 불필요한 중복 요청을 피하기 위해 캐싱 전략을 적용할 수 있습니다. 클라이언트 측에서는 Apollo Client와 같은 라이브러리에서 캐싱 기능을 자동으로 지원하며, 서버 측에서는 Redis 같은 캐시 서버를 사용할 수 있습니다.

Apollo Client 캐싱: Apollo Client는 쿼리 결과를 자동으로 캐싱하고, 같은 쿼리가 반복되면 네트워크 요청을 보내지 않고 캐시된 데이터를 사용합니다.

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

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

이처럼 Apollo Client에서는 캐시가 기본적으로 설정되어 있으며, 데이터 요청이 있을 때마다 캐시를 먼저 확인하고, 필요한 경우에만 서버로 요청을 보냅니다.

3. 인증(Authentication) 및 권한 관리(Authorization)

GraphQL에서는 클라이언트가 요청하는 데이터가 민감할 수 있기 때문에, 인증권한 관리가 중요합니다. 이를 위해 보통 **JWT (JSON Web Token)**이나 세션 기반 인증을 사용합니다.

1) JWT를 이용한 인증

클라이언트가 서버에 요청을 보낼 때, JWT 토큰을 HTTP 헤더에 포함하여 인증을 수행할 수 있습니다.

JWT 토큰을 헤더에 포함:

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql'
});

const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('authToken');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
}
};
});

const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});

위의 예시에서 Apollo Client는 요청을 보내기 전, JWT 토큰을 자동으로 HTTP 헤더에 추가합니다. 서버 측에서는 이 토큰을 검증한 후 클라이언트의 권한을 확인할 수 있습니다.

2) 서버에서 권한 관리

서버 측에서는 인증된 사용자만 특정 쿼리나 뮤테이션에 접근할 수 있도록 리졸버에서 인증을 확인할 수 있습니다.

권한 관리 예시:

javascript코드 복사const resolvers = {
  Query: {
    secretData: (parent, args, context) => {
      if (!context.user) {
        throw new Error('Not authenticated');
      }
      return "This is secret data!";
    }
  }
};

Context 객체를 사용해 요청을 처리하기 전에 사용자가 인증되었는지 확인합니다. 인증되지 않은 사용자는 해당 쿼리나 뮤테이션에 접근할 수 없습니다.

결론

  • 서브스크립션: 실시간 데이터 업데이트를 위해 사용되며, WebSocket을 통해 서버와 실시간 연결을 유지합니다.
  • 데이터 최적화: DataLoader를 사용해 데이터베이스 쿼리 효율을 높이고, Apollo Client 같은 도구를 통해 캐싱을 구현할 수 있습니다.
  • 인증 및 권한 관리: JWT 토큰을 사용해 클라이언트를 인증하고, 권한이 있는 사용자만 데이터에 접근할 수 있도록 합니다.

작성일 댓글 남기기

GraphQL 스키마 확인하기

1. GraphQL 스키마는 명시적으로 제공

GraphQL 서버는 스키마를 명시적으로 정의합니다. 이 스키마는 클라이언트가 어떤 데이터 타입과 필드를 요청할 수 있는지를 서버에서 정의하는 설계도입니다. 그렇기 때문에 클라이언트가 요청할 수 있는 데이터 구조를 미리 알기 위해서는 서버에서 스키마를 확인해야 합니다.

2. 스키마를 확인하는 방법

클라이언트는 여러 가지 방법을 통해 서버의 스키마 정보를 확인할 수 있습니다.

1) GraphiQL 인터페이스를 통해 스키마 탐색

GraphiQL 같은 개발 도구를 사용하면, 서버의 스키마를 시각적으로 탐색할 수 있습니다.

  • GraphiQL은 보통 http://localhost:4000/graphql과 같은 주소로 제공되며, 서버에서 graphiql: true로 활성화되었을 때 사용할 수 있습니다.
  • 이 인터페이스에서 제공하는 “Docs” 탭을 열면, 서버가 제공하는 스키마 정보(쿼리와 뮤테이션의 구조, 필드 타입 등)를 탐색할 수 있습니다.

이 툴을 통해 클라이언트는 서버가 제공하는 모든 쿼리와 뮤테이션, 그리고 각 필드의 타입, 인자 등을 쉽게 알 수 있습니다.

2) Introspection 쿼리를 사용하여 스키마 가져오기

GraphQL은 Introspection이라는 메커니즘을 통해 클라이언트가 서버의 스키마를 쿼리로 요청할 수 있게 해줍니다. Introspection 쿼리를 사용하면 서버가 제공하는 전체 스키마 정보를 JSON 형식으로 받아올 수 있습니다.

다음은 Introspection 쿼리 예시입니다:

{
__schema {
types {
name
fields {
name
}
}
}
}

이 쿼리는 서버가 제공하는 모든 타입과 각 타입의 필드를 반환해줍니다. 클라이언트는 이 쿼리를 통해 서버에서 어떤 데이터 구조를 제공하는지 알 수 있습니다.

서버는 일반적으로 Introspection 기능을 기본적으로 활성화해 두지만, 보안이나 성능상의 이유로 일부 서버에서는 비활성화할 수 있습니다. 이를 통해 클라이언트는 어떤 쿼리나 뮤테이션을 보낼 수 있는지 파악할 수 있습니다.

3) GraphQL SDL 문서 제공

서버는 **GraphQL SDL (Schema Definition Language)**로 스키마를 정의합니다. 이 SDL은 주로 개발자가 서버에서 작성하며, 서버가 제공하는 API 문서에 포함되어 있을 수 있습니다. 서버 측에서 API 문서를 통해 스키마를 명시적으로 제공할 수도 있습니다.

예를 들어, 문서로 제공된 스키마가 다음과 같이 생겼을 수 있습니다:

graphql코드 복사type Query {
user(id: ID!): User
users: [User]
}

type User {
id: ID!
name: String!
email: String!
}

type Mutation {
addUser(name: String!, email: String!): User
}

이 문서를 보면 클라이언트는 어떤 쿼리와 뮤테이션이 가능한지, 어떤 필드가 있는지 쉽게 파악할 수 있습니다.

3. 클라이언트에서 Apollo Client로 스키마 읽어오기

Apollo Client와 같은 고급 클라이언트 라이브러리는 Introspection을 자동으로 실행해 스키마를 미리 가져와 사용하기도 합니다. 이를 통해 개발자가 어떤 쿼리를 보낼 수 있는지 쉽게 파악할 수 있고, 클라이언트 측에서 스키마에 대한 자동 완성 기능까지 사용할 수 있습니다.

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

const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache(),
introspection: true, // Introspection 쿼리를 자동으로 실행
});

client
.query({
query: gql`
query {
__schema {
types {
name
fields {
name
}
}
}
}
`,
})
.then(result => console.log(result));

이처럼 클라이언트는 Introspection을 통해 서버에서 제공하는 스키마를 받아올 수 있고, 이를 통해 서버의 모든 쿼리와 뮤테이션, 데이터 구조를 파악할 수 있습니다.

결론

  • 명시적 스키마 제공: 서버는 반드시 스키마를 명시적으로 정의하고 클라이언트에 제공해야 합니다. 그래야 클라이언트가 어떤 데이터를 요청할 수 있는지 알 수 있습니다.
  • Introspection 쿼리: 클라이언트는 Introspection 쿼리를 통해 서버의 스키마를 요청할 수 있습니다.
  • GraphiQL: GraphiQL 같은 개발 도구를 사용하면 스키마를 쉽게 탐색할 수 있습니다.

서버 측에서 Introspection을 제공하는지 여부에 따라 클라이언트는 해당 메커니즘을 사용하여 스키마를 동적으로 파악할 수 있습니다. 스키마를 미리 문서화해 제공하거나, GraphiQL 같은 도구를 통해 탐색할 수도 있습니다.