Halo World

[GraphQL] GraphQL 서버 깊이 파보기 (1) 본문

개발 지식/DEVELOPMENT

[GraphQL] GraphQL 서버 깊이 파보기 (1)

_Yeony 2022. 2. 13. 15:39

본 포스팅은 Inflearn 얄코님 강의를 수강하며 복습용으로 작성하였습니다.

https://www.inflearn.com/course/%EC%96%84%ED%8C%8D%ED%95%9C-graphql-apollo/dashboard

 

[무료] 얄팍한 GraphQL과 Apollo - 인프런 | 강의

⚡ 짧고 굵은 전체 90분 강좌! 사이트의 코드들을 복붙하며 빠르게 GraphQL을 배우고 아폴로 사용법을 익히세요., - 강의 소개 | 인프런...

www.inflearn.com

 

 

서버 구성요소 모듈화


이전 강의에서 살펴본 typeDefs와 Resolver는 모두 index.js 한 파일에 작성을 한 형태였는데,

각각을 모듈별로 쪼개어 파일을 구성하여 사용할 수 있다.

 

https://www.apollographql.com/docs/apollo-server/api/apollo-server/

아폴로 서버 문서를 보면, 배열 형태로 typeDefs와 Resolvers를 구성할 수 있도록 되어있다.

  • typeDefs : 단일 변수 또는 배열로 지정 가능
  • resolvers : 단일 Object 또는 Merge된 배열로 가능
  • 각각의 파일에 query 및 mutation 루트 타입 정의
//typedefs-resolvers/_queries.js

// ...
const typeDefs = gql`
    type Query {
        equipments: [Equipment]
    }
`
// ...
//typedefs-resolvers/_mutations.js

// ...
const typeDefs = gql`
    type Mutation {
      deleteEquipment(id: String): Equipment
    }
`
// ...
  • equipments 데이터의 typeDefs와 resolver 별도 정의
//typedefs-resolvers/equipments.js

// ...
const typeDefs = gql`
    type Equipment {
        id: String
        used_by: String
        count: Int
        new_or_used: String
    }
`
const resolvers = {
    Query: {
        equipments: (parent, args) => dbWorks.getEquipments(args),
    },
    Mutation: {
        deleteEquipment: (parent, args) => dbWorks.deleteItem('equipments', args),
    }
}
// ...
  • index.js에서 각각의 파일들을 import 해준 후, typeDefs와 resolvers 선언 시 배열로 넣어주어 모듈화 가능
//typedefs-resolvers/index.js

// ...
const queries = require('./typedefs-resolvers/_queries')
const mutations = require('./typedefs-resolvers/_mutations')
const equipments = require('./typedefs-resolvers/equipments'
// ...
const typeDefs = [
    queries,
    mutations,
    equipments.typeDefs,
]
const resolvers = [
    equipments.resolvers
]
// ...
  • 추가로 supply 라는 새로운 모듈을 추가 한다면, Equipment와 동일하게 해당 typeDef와 resolver를 supplies.js 라는 파일에 정의하고, query와 mutation 파일에 해당 쿼리를 정의해주는 방식으로 추가 가능하다.
    https://www.yalco.kr/@graphql-apollo/3-1/

 

GraphQL의 기본 타입들


1. 스칼라 타입

GraphQL 내장 자료형

    type EquipmentAdv {
        id: ID!
        used_by: String!
        count: Int!
        use_rate: Float
        is_new: Boolean!
    }
  • ID : 기본적으로는 String이나, 고유 식별자 역할임을 나타냄
  • String : UTF-8 문자열
  • Int : 부호가 있는 32비트 정수
  • Float : 부호가 있는 부동소수점 값
  • Boolean : 참/거짓
  • ! 은 Non null을 의미하며, null인 경우 서버 내부에서 오류 반환

 

//equipment.js 내부에 equipmentAdvs를 위한 resolver 정의
//user_rate, is_new를 해당하는 스칼라 타입으로 변환
const resolvers = {
    Query: {
        // ...
        equipmentAdvs: (parent, args) => dbWorks.getEquipments(args)
            .map((equipment) => {
                if (equipment.used_by === 'developer') {
                    equipment.use_rate = Math.random().toFixed(2)
                }
                equipment.is_new = equipment.new_or_used === 'new'
                return equipment
            }),
    },
    // ...
}
//_queries.js
    type Query {
        ...
        equipmentAdvs: [EquipmentAdv]
        ...
    }
  • playground에서 테스트
query {
    equipmentAdvs {
        id
        used_by
        count
        use_rate
        is_new
    }
}

 

 

2. 열거 타입

  • Enum 타입의 데이터인 경우 enum 타입을 생성하여 데이터 타입을 지정해 줄 수 있다.
  • 객체에 데이터가 Enum 타입에 정의되어 있지 않은 타입으로 지정되는 경우 Error가 발생한다.
//_enums.js  enum 예제
//Role 타입으로 설정된 데이터는 developer, desiner, planner로만,
//NewOrUsed 타입으로 설정된 데이터는 new, used 두 가지 값만 가능하다.

const { gql } = require('apollo-server')
const typeDefs = gql`
    enum Role {
        developer
        designer
        planner
    }
    enum NewOrUsed {
        new
        used
    }
`
module.exports = typeDefs
//index.js의 typeDefs에 enums 타입 추가

// ...
const enums = require('./typedefs-resolvers/_enums')
// ...
const typeDefs = [
    // ...
    enums,
    // ...
]
//equipments.js

const typeDefs = gql`
    type Equipment {
        id: ID!
        used_by: Role! //Role 타입에 설정되어있는 값만 들어갈 수 있다.
        count: Int!
        new_or_used: NewOrUsed! //NewOrUsed 타입에 설정되어있는 값만 들어갈 수 있다.
    }
    type EquipmentAdv {
        id: ID!
        used_by: Role!
        count: Int!
        use_rate: Float
        is_new: Boolean!
    }
`
  • playground에서 쿼리를 실행하여 결과를 확인해 볼 수 있다.
  • db에 enums에 설정되어있지 않은 값이 들어있으면 (예를 들어, used_by 값이 manager로 설정되어있으면) 쿼리 실행 중 에러가 발생한다.
query {
  equipments {
    id
    used_by
    count
    new_or_used
  }
equipmentAdvs {
    id
    used_by
    count
    use_rate
    is_new
  }
}

 

3. 리스트 타입

  • 특정 타입의 배열을 반환
//equipments.js

const typeDefs = gql`
    // ...
    type EquipmentAdv {
        id: ID!
        used_by: Role!
        count: Int!
        use_rate: Float
        is_new: Boolean!,
        users: [String!]
    }
`
// ...
const resolvers = {
    Query: {
        // ...
        equipmentAdvs: (parent, args) => dbWorks.getEquipments(args)
            .map((equipment) => {
                if (equipment.used_by === 'developer') {
                    equipment.use_rate = Math.random().toFixed(2)
                }
                equipment.is_new = equipment.new_or_used === 'new'
                
                // 리스트 예제 
                // equipment 결과값의 1/2을 대상으로 
                // user라는 배열을 추가하고, 
                // db에 저장된 equipment.used_by와 같은 role을 갖고 있는 사람 중 1/5 확률로 
                // user 배열에 추가함
                if (Math.random() > 0.5) {
                    equipment.users = []
                    dbWorks.getPeople(args).forEach((person) => {
                        if (person.role === equipment.used_by && Math.random() < 0.2) {
                            equipment.users.push(person.last_name)
                        }
                    })
                }
                return equipment
            }),
    },
    // ...
}

 

선언부 users: null users: [] users[..., null]
[String] 가능 가능 가능
[String!] 가능 가능 불가능
[String]! 불가능 가능 가능
[String!]! 불가능 가능 불가능

 

 

4. 객체 타입

  • 사용자에 의해 정의된 타입들
  • ex) typeDefs, equipment 등..