웹 앱 API 개발을 위한 GraphQL 1~4장 정리

웹 앱 API 개발을 위한 GraphQL 1~4장 정리

1장 : 서론


GraphQL이란 무엇인가?

GraphQL은 선언형 데이터 패칭 언어로 일컬어 진다. 개발자는 무슨 데이터가 필요한지에 대한 요구사항만 작성하면 되고 어떻게 가져올지는 신경쓰지 않아도 된다. GraphQL은 서버와 클라이언트의 통신을 위한 명세이다. 문법과 타입 시스템의 실행과 유효성 검사에 대한 스펙을 가지고 있다. 그 외에 별다른 지침은 없으므로 서비스에 따라서 설계할 수 있다.

설계 원칙

  • 위계적
    • GraphQL 쿼리는 위계성을 가진다. 필드 안에 필드가 중첩될 수 있고 쿼리와 그에 대한 반환 데이터는 형태가 같다.
  • 제품 중심적
  • 엄격한 타입 제한
    • GraphQL 서버는 GraphQL 타입 시스템을 사용한다. 이를 토대로 유효성 검사를 받는다.
  • 클라이언트 맞춤 쿼리
  • 인트로스펙티브
    • GraphQL 언어를 사용해 GraphQL 서버가 사용하는 타입 시스템에 대한 쿼리를 작성할 수 있다.

왜 필요한가?

데이터 전송 방식은 아래 단계를 거치며 진화했다.

  1. RCP (1960) - Remote Procedure Call
  2. SOAP (1990) - Simple Object Access Protocol (XML)
  3. REST (2000) - Representational State Transfer

REST는 현존하는 훌륭한 스펙이지만 아래와 같은 단점을 가지고 있다.

  • 오버패칭: 필요한 데이터 보다 더 받아옴
  • 언더패칭: 필요한 데이터가 부족하여 여러번 요청함
  • 복잡한 엔드포인트: 기능이 추가될 수록 엔드포인트가 복잡해짐

GraphQL은 위 문제를 해결할 수 있는 하나의 수단이 될 수 있다.

GraphQL 단점

[개인적으로 정리한 부분]

GraphQL은 장점만 있는가? 그렇지 않다. 아래와 같은 단점을 가지고 있다.

  • 캐싱: GraphQL은 기본적으로 HTTP 캐싱을 지원하지 않는다. 이로 인해 RESTful API에서와 같이 캐싱을 구현하기가 더 어려울 수 있다.
  • 보안: GraphQL 쿼리는 클라이언트가 필요한 만큼의 데이터만 요청할 수 있지만, 이것이 곧 과도한 쿼리 또는 무한 루프를 통한 공격을 방지하지 않는다는 것을 의미한다. 따라서 적절한 보안 메커니즘을 구현해야 한다.
  • 쿼리 오버헤드: GraphQL은 클라이언트가 필요한 데이터만 요청할 수 있게 해준다. 하지만 이것은 서버 측에서 쿼리를 해석하고 처리하는 오버헤드를 야기할 수 있다. 특히 복잡한 쿼리를 처리할 때 이러한 오버헤드가 더 두드러질 수 있다.


2장 : 그래프 이론


  • 그래프 이론의 개념은 레오넬 오일러가 17 세기에 쾰니히스베르크의 다리 문제를 해결함으로써 널리 알려져있다.
  • 그래프 이론은 객체 간의 관계를 표현하는 수학적 모델로 각 객체는 노드(node) 혹은 정점(vertax)와 간선(edge)의 집합으로 나타낸다. 노드는 특정 개체나 이벤트를 나타내며, 간선은 노드 간의 관계를 나타낸다. 그래프 이론은 많은 곳에서 응용되고 있으며 객체간의 네트워크 구조를 파악할 수 있다.
  • 그래프는 간선이 방향을 가진 방향 그래프와 간선의 방향과 위계가 없는 무방향 그래프로 분류된다.


3장 : GraphQL 쿼리어


  • IBM에서 개발한 관계형 데이터베이스 언어인 구조화된 영문 쿼리 언어(Structured English Query Language = SEQUEL, 이하 SQL)은 도메인에 종속된 언어로 CRUD로 데이터를 관리하는데 초점이 맞춰져 있고 SQL의 철학이 REST에 큰 영향을 주었다.
  • REST의 구조에서 발생할 수 있는 단점을 극복하기 위해서 GraphQL은 데이터베이스를 질의하는 SQL의 개념을 바탕으로 웹에서 쿼리문을 서버로 질의할 수 있도록 고안되었다. GraphQL 질의문은 어휘 분석을 통해 추상 구문 트리로 파싱되어 유효성 검사를 거친다.
  • GraphQL과 SQL 구문은 다음과 같은 차이를 가지고 있다.
    • Read
      • SQL : SELECT
      • GraphQL : Query
    • Create, Update, Delete
      • SQL : INSERT, UPDATE, DELETE
      • GraphQL : Mutation

Structure of Query

query { // Root Type = 작업의 시작
    allSongs { // Selection Set = 필드 선택
        name
        time:  duration // Alias = 별칭
        artist {
            name
        }
        album {
            name
            cover {
                src
            }
        }
    }
    artists: allArtist(offset: 0, limit: 10) { // Query Arguments = 결과 필터링
    ...
    }
}
  • 필드는 스칼라(scalar) 타입과 객체(object) 타입 둘 중 하나에 속하게 된다.
    • 스칼라 타입 = 원시 타입
      • ID (String과 같은 형태로 반환되지만 항상 유니크한 값)
      • Int
      • Float
      • String
      • Boolean
    • 객체 타입 = 스키마에 정의한 필드를 그룹으로 묶은 것
  • 프래그먼트
    • 중복되는 질의문을 줄여줌
fragment artistInfo on artist {
    name
    debutAt
    LatestAlbumReleaseAt
}

query {
    allSongs {
        artist {
            ...artistInfo
        }
    }
    allArtists {
        ...artistInfo
    }
}
  • 유니온 타입

    • 특정 객체 필드에서 서로 다른 타입이 내려오도록 함
  • 인터페이스 (두 요소는 4장에서 자세히)

Structure of Mutation

mutation {
    createArtist(name: "Jino Bae") {
        id
        name
        createdAt
    }
}
mutation {
    updateArtist(id: "10", name: "Jino Bae") {
        name
        createdAt
    }
}
mutation {
    deleteArtist(id: "10")
}

Introspection

query {
    __schema {
        types {
            name
            description
        }
    }
}
query {
    __type(name: 'artist') {
        name
        fields {
            name
            description
            type {
                name
            }
        }
    }
}
fragment typeFields on __Type {
    name
    fields {
        name
    }
}

query {
    __scheme {
        queryType {
            ...typeFields
        }
        
        mutationType {
            ...typeFields
        }
    }
}


4장 : 스키마 설계


GraphQL API를 구현하는데 있어 도메인에 국한되기 보다는 스키마 (데이터 모델링) 기반으로 소프트웨어를 설계해야 한다. 따라서 반환할 데이터 타입에 대해서 생각해보고 이를 제대로 정의해야 한다. GraphQL로 API를 설계하면 API가 엔드포인트의 결합(REST)가 아니라 타입의 결합으로 보인다.

Type

GraphQL의 핵심 단위는 타입이다.

type Album {
    id: ID! // ! = non-nullable
    name: String!
    description: String // nullable
}

Scalar Type

기본 스칼라 타입은 5개 이지만 원한다면 커스텀한 스칼라 타입을 생성할 수 있다.

scalar DateTime

type Album {
    ...
    createdAt: DateTime!
}

생성한 스칼라 타입의 직렬화 및 유효성 검사를 처리할 수 있다.

const resolvers = {
    DateTime: {
        // 예시: JavaScript에서 Date 객체를 사용하여 처리
        serialize(value) {
        return new Date(value).getTime();
    },
    // 예시: 클라이언트에서 전달된 값을 Date 객체로 변환
    parseValue(value) {
        return new Date(value);
    },
    // 예시: 쿼리나 뮤테이션에서 리턴된 값 처리
    parseLiteral(ast) {
        if (ast.kind === Kind.INT) {
            return new Date(parseInt(ast.value, 10));
        }
        return null;
        },
    },
    ...
}

Enum Type

enum AlbumGenre {
    POP
    JAZZ
    HIPHOP
    ELECTRONIC
}

type Album {
    ...
    genre: AlbumGenre!
}

List Type

리스트 선언정의
[Int]리스트 안에 담긴 정수 값은 null이 될 수 있다.
[Int!]리스트 안에 담긴 정수 값은 null이 될 수 없다.
[Int]!리스트 안에 정수 값은 null이 될 수 있으나, 리스트 자체는 null이 될 수 없다.
[Int!]!리스트 안의 정수 값은 null이 될 수 없고, 리스트 자체도 null이 될 수 없다.

1:1

커스텀 객체 타입으로 필드를 만들면 두 객체는 서로 연결(Edge가 형성)된다.

type Artist {
    name: String!
}

type Album {
    artist: Artist!
    name: String!
}

1:N

GraphQL 서비스는 최대한 방향성이 없도록 만드는게 좋다. 따라서 위 타입에서 Artist에서 Album로 되돌아 갈 수 있는 패스가 있어야 한다.

type Artist {
    name: String!
    albums: [Album!]!
}

N:M

앨범에는 여러명의 아티스트가 참여할 수 있으므로

type Artist {
    name: String!
    albums: [Album!]!
}

type Album {
    artists: [Artist!]!
    name: String!
}

통과 타입(through type)을 만드는 경우 엣지를 커스텀 객체 타입으로 정의하면 된다.

type Ablum {
    collaborators: [Collaborator!]!
    name: String!
}

type Collaborator {
    artists: [Artist!]!
    album: Album!
    firstCollaborateAt: DateTime
    latestColleaborateAt: DateTime
}

Union Type

한 배열에 서로 다른 객체가 내려가도록 만들 수 있다.

union Agenda = StudyGroup | Workout

type StudyGroup {
    name: String!
    subject: String
    students: [User!]!
}

type Workout {
    name: String!
    reps: Int!
}

type Query {
    agenda: [Agenda!]!
}
query schedule {
    agenda {
        ... on Workout {
            name
            reps
        }
        ... on StudyGroup {
            name
            subject
            students
        }
    }
}

Interface

한 필드 안에 여러 타입을 넣을 때 사용하는데, 객체 타입 용도로 만드는 추상 타입이며, 스키마 코드를 조직할 때 아주 좋은 방법이다. 인터페이스를 통해서 특정 필드가 반드시 포함되도록 만들 수 있으며, 이들 필드는 쿼리에서 사용할 수 있다.

scalra DateTime

interface Agenda {
    name: String!
    start: DateTime!
    end: DateTime!
}

type StudyGroup implements Agenda {
    name: String!
    start: DateTime!
    end: DateTime!
    participants: [User!]!
    topic: String!
}

type Workout implements Agenda {
    name: String!
    start: DateTime!
    end: DateTime!
    reps: Int!
}

```rs
query schedule {
    agenda {
        name
        start
        end
        ... on Workout {
            reps
        }
    }
}

Argument

enum ArtistOrderBy {
    id
    name
}

enum ArtistOrderDirection {
    ASCENDING
    DESCENDING
}

type Query {
    artist(id: ID!): Artist!
    allArtists(
        offset: Int = 0,
        limit: Int = 20,
        orderBy: ArtistOrderBy = id,
        orderDirection: ArtistOrderDirection = DESCENDING
    ): [Artist!]!
}
query {
    artist(id: "1") {
        name
    }

    allArtists(offset: 0, limit: 30) {
        id
        name
    }
}

Mutation

엄밀히 말하면 뮤테이션과 쿼리 작성법에는 큰 차이가 없다. 어플리케이션의 상태를 바꿀 액션이나 이벤트가 있을 때만 뮤테이션을 작성해야 한다. 뮤테이션은 어플리케이션의 동사 역할을 해야한다.

type Mutation {
    createArtist(name: String!): Artist
    updateArtist(id: ID!, name: String!): Artist
    deleteArtist(id: ID!): Boolean
}

scheme {
    query: Query,
    mutation: Mutation
}

Input Type

위에서 페이지네이션을 처리하거나 뮤테이션에서 인자 값이 많아진다면 관리가 어려워 질 수 있다. 그럴때는 Input 타입을 사용하면 체계적으로 관리할 수 있다.

input Pagination {
    offset: Int = 0
    limit: Int = 20
}

input Order {
    orderBy: String!
    orderDirection: String!
}

type Query {
    allArtist(pagintation: Pagination, order: Order)
}

쿼리 변수에 대해서는 좀 더 알아봐야 겠다.

Return Type

OAuth 등등을 적용하다보면 커스텀한 리턴값을 돌려줘야 하는 경우도 있다.

type AuthPayload {
    user: User!
    token: String!
}
type Mutation {
    OAuth(code: String!): AuthPayload!
}

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.