티스토리 뷰
스키마(Schema)
데이터 타입의 집합, API에 반환할 데이터의 타입에 대해서 생각해야 하고, 이 타입에 대해서 충분한 이야기를 나누고, 제대로 정의해두어야 한다.
스키마 우선주의(Schema First)는 디자인 방법론으로 이를 통해서 모든 팀원이 데이터 타입에 대해서 숙지할 수 있도록 유도한다.
백엔드 팀에서는 어떤 데이터를 저장하고 전달해야 하는지 이해할 수 있고,
프런트엔드 팀에서는 사용자 인터페이스를 작업할 때 필요한 데이터를 정의할 수 있다.
GraphQL에서도 스키마 정의를 위해 SDL(Schema Definition Language)를 지원한다.
프로그램 언어, 프레임워크에 상관없는 GraphQL의 쿼리 언어처럼, 사용법이 항상 동일하다.
SDL을 통해 GraphQL 스키마 문서를 만 게 되는데 이 문서는 애플리케이션에서 사용할 타입을 정해둔 텍스트 문서다. 문서를 통해 정의된 타입은 나중에 쿼리 언어를 통해 GraphQL 요청을 하게 되었을 때 유효성 검사에 사용된다.
타입 정의하기
타입(Type)은 커스텀 객체이고, 타입을 사용하는 애플리케이션의 성격을 알 수 있는 특징이다.
필드(Field)는 특정 종류의 데이터를 반환한다.
type Photo {
id: ID!
name: String!
url: String!
description: String
}
# 필드에 포함되어있는 !는 return 되었을 때 반드시 데이터를 포함하고 있어야하는 필드를 의미한다.
# ID 타입은 고유 식별자 값이 return 되어야하는 필드이다.
필드에 들어가는 타입은 다음과 같다.
스칼라 타입(Scala Type)
기본적으로 String, Number, ID, Int, Float, Boolean을 사용하지만, 직접 스칼라 타입을 커스텀하여 사용할 수 있다. 객체 타입과는 다르기 때문에 하위 필드를 가지진 않지만, 직접 제작한 방식으로 유효성 검사가 가능한 스칼라 타입을 만들 수 있다.
type Photo {
id: ID!
name: String!
url: String!
description: String
created: DateTime!
}
열거 타입(Enumeration Type)
스칼라 타입에 속하면서, 필드에 반환하는 문자열 값을 세트로 미리 지정할 수 있다. 필드에서 반환되는 값은 세트 안에 존재하는 값만 가능하다.
enum PhotoCategory {
SELFIE
PORTRAIT
ACTION
LANDSCAPE
GRAPHIC
}
위처럼 만든 열거 타입을 아래와 같이 사용할 수 있다.
type Photo {
id: ID!
name: String!
url: String!
description: String
created: DateTime!
category: PhotoCategory!
}
연결과 리스트
스키마의 필드에는 GraphQL이 담긴 리스트도 반환이 가능하다.
예) [String]은 문자열 리스트를 의미한다.
필드의 데이터 여부를 표현하는! 를 리스트 타입에서도 사용할 수 있다.
리스트 타입을 사용할 때! 의 위치에 따라 의미 차이가 크기 때문에 주의해서 사용해야 한다.
[Int] | 리스트 안에 담긴 정수 값은 Null이 될 수 있다. |
[Int!] | 리스트 안에 담긴 정수 값은 Null이 될 수 없다. |
[Int]! | 리스트 안에 담긴 정수 값은 Null이 될 수 있지만, 리스트 자체는 Null이 될 수 없다. |
[Int!]! | 리스트 안에 담긴 정수 값도 Null이 될 수 없고, 리스트 자체도 Null이 될 수 없다. |
GraphQL에는 데이터를 요청할 때 데이터와 관련된 데이터의 필드까지 요청할 수 있는 쿼리 기능이 있다.
다양한 연결 타입을 통해 여러 객체 타입을 연결할 수 있다.
일대일 연결
두 객체 사이의 연결을 에지(Edge)라고 한다.
일대일 연결은 하나의 객체가 또 다른 객체 타입과 서로 연결되어있는 연결 타입을 말한다.
type User {
githubLogin: ID!
name: String
avatar: String
}
type Photo {
id: ID!
name: String!
url: String!
description: String
created: DateTime!
category: PhotoCategory!
postedBy: User!
}
일대다 연결
쿼리를 자유롭게 만들기 위해서 단방향으로 설계하는 것은 지양해야 한다.
A타입에 B타입을 사용한다면, B타입에서도 A타입으로 돌아갈 수 있는 방법을 만들어 두어야 한다.
앞서 일대일 연결에서 만든 Uset 타입은 현재 Photo에서 User로 단방향 연결되어 있다.
이것을 아래와 같이 수정하여 양방향 연결로 만들 수 있다.
그리고 타입 리스트를 반환하도록 하여 한 User에 대해서 여러 Photo를 가리킬 수 있도록 만들 수 있다.
type User {
githubLogin: ID!
name: String
avatar: String
postedPhoto: [Photo!]!
}
다대다 연결
노드 리스트를 다른 노드 리스트와 연결하는 것을 표현할 수 있는 연결 타입이다.
일대다 연결에서 표현한 방식처럼 양쪽 타입에 리스트 타입 필드를 만들면 다대다 연결을 표현할 수 있다.
type User {
githubLogin: ID!
name: String
avatar: String
uploadedPhoto: [Photo!]!
}
type Photo {
id: ID!
name: String!
url: String!
description: String
created: DateTime!
category: PhotoCategory!
postedBy: User!
taggedUser: [User!]!
}
- 통과 타입
위를 친구사이 관계를 표현하는 타입이라고 할 때 User 타입 리스트로 각 유저 간 친구 관계를 나타내고, howLong 필드를 통해 User들의 관계에 대한 정보도 담고 있는 것을 확인할 수 있다.type FriendShip { friends: [User!]! howLong: Int! }
- 다대다 연결을 사용할 때 연결 자체에 대한 정보를 담고 싶을 때 사용하는 타입이다.
여러 타입을 담는 리스트
유니언 타입(Union Type)
여러 타입 가운데 하나의 타입만 반환할 수 있다.
union AgendaItem = StudyGroup | Workout
type Query {
agenda: [AgendaItem!]!
}
AgendaItem으로 정의한 필드는 StudyGroup 또는 Workout 타입만 들어갈 것이고, 결국 agenda 필드는 StudyGroup과 Workout 타입 데이터로 구성된 리스트 데이터를 가지게 된다.
인터페이스(Interface)
객체 타입 용도로 만드는 추상 타입이다. 스키마 코드의 구조를 조직할 때 아주 유용한 타입이다.
인터페이스를 통해 정의한 필드는 반드시 특정 타입에 포함되어야 한다.
interface AgendaItem {
name: String!
start: DateTime!
end: DateTime!
}
type Workout implements AgendaItem {
name: String!
start: DataTime!
end: DataTime!
reps: Int!
}
Workout 타입은 AgendaItem이라는 인터페이스를 기반으로 만들어졌기 때문에 name, start, end 필드가 무조건 포함되어야 한다.
객체에 따라 내부의 필드 값이 달라질 경우에는 유니언 타입을 사용하는 것이 더 바람직하고,
특정 필드가 반드시 포함되어야 하는 경우에는 인터페이스를 쓰는 것이 바람직하다.
인자
GraphQL의 필드에는 인자를 활용할 수 있다.
인자를 사용하면 인자에 따라 특정 데이터가 반환되도록 할 수 있다.
- 데이터 필터링
위 예시에서 PhotoCategroy가 SELFIE인 데이터에 대해서 필터링된 데이터가 반환될 것이다.type Query { ... allPhotos(category: PhotoCategory): [Photo!]! } query { allPhoto(categroy: "SELFIE") { name description url } }
부가적인 파라미터로 인자를 전달받고, 전달받은 인자로 데이터를 필터링하여 그에 해당하는 데이터만 보여줄 수 있다. - 데이터 페이징(Data Paging) first라는 인자를 통해 페이지 한 장당 들어가는 데이터의 수를 전달받고, start 인자를 통해 페이징을 시작할 index 정보를 전달받아 사용할 수 있다.
type Query {
...
allUsers(first: Int=50, start: Int=0): [User!]!
allPhotos(first: Int=25, start: Int=0): [Photo!]!
}
# User정보는 0번째 데이터부터 50명분의 데이터를 전달받는다.
# Photo정보는 0번째 데이터부터 25명분의 데이터를 전달받는다.
인자를 통해 한번에 전달받을 데이터의 양을 조절할 수 있다.
- 정렬
쿼리를 실행했을 때 전달받은 사진 데이터는 카테고리 필드를 기준으로 내림차순으로 정렬된 상태가 될 것이다.enum SortDirection { ASCENDING DESCENDING } enum SortablePhotoField { name description category created } Query { allPhotos( sort: SortDirection = DESCENDING sortBy: SortablePhotoField = category ): [Photo!] }
- 인자를 통해 리스트 정렬 방식을 지정해줄 수 있다.
위와 같이 인자를 통해 페이징, 필터링, 정렬을 사용하여 데이터 양을 조절한 것처럼, 쿼리에서 return 받는 데이터의 양을 세밀하게 조절하는 것이 가능하다.
뮤테이션
뮤테이션도 쿼리처럼 반드시 스키 마안에 정의되어야 한다.
쿼리와 작성법에 차이는 없지만, 애플리케이션의 동작을 나타내는 동사 역할을 할 수 있도록 제작해야 한다.
type Mutation {
postPhoto(
name: String!
description: String
category: PhotoCategory = PORTRAIT
)
}
schema {
query: Query
mutation: Mutation
}
뮤테이션을 실행하면 정의한 동작을 수행하고, 아래 정의한 필드에 해당하는 데이터를 전달받는다.
뮤테이션을 작성할 때는 변수를 선언하는 것이 좋다.
뮤테이션을 통해 사용자 데이터를 다량으로 생산할 때 좋고, 클라이언트에서 뮤테이션을 작성할 때도 도움이 된다.
mutation postPhoto (
$name: String
$description: String
$category: PhotoCategory
) {
postPhoto(name: $name, description: $description, category: $category) {
id
name
email
}
}
인풋 타입
쿼리와 뮤테이션의 인자 길이가 길어졌을 때 인풋 타입으로 관리 가능하다.
input PostPhotoInput {
name: String!
description: String
category: PhotoCategory = PORTRAIT
}
type Mutation {
postPhoto(input: PostPhotoInput!): Photo!
}
input 인자의 타입이 PostPhotoInput으로 받을 때, name, description의 값은 인자로 넘겨받아야 하고, category는 꼭 들어갈 필요는 없다.
input 타입을 통해 GraphQL 스키마를 깔끔하게 유지할 수 있고, input 타입은 모든 필드에서 사용할 수 있기 때문에 코드 구조 체계적으로 개선에 부분에 도움을 준다.
리턴 타입
단순한 페이로드 데이터 이외에 추가로 원하는 데이터 필드가 있을 경우 커스텀 객체 타입을 통해 필드를 추가하면 추가된 데이터를 포함하여 데이터를 받을 수 있다.
예를 들어 OAuth를 통해 사용자 인증을 거쳐 토큰을 받아오는 상황에서 리턴 타입을 통해 추가적으로 요청이 발생한 시간, 응답에 걸린 시간, 응답에 포함된 항목 개수와 같은 필드를 추가하여 필요한 데이터를 생성해서 전달받을 있다.
서브스크립션(Subscription)
실시간 데이터를 다룰 때 좋은 방법으로 실시간으로 데이터가 전송될 때 서브스크립션을 통해 원하는 데이터를 요청하여 받을 수 있다.
type Subscription {
newPhoto(category: PhotoCategory): Photo!
newUser: User!
}
schema {
...
subscription: Subscription
}
subscription {
newPhoto(category: "ACTION") {
id
name
url
postedBy {
name
}
}
}
새로운 사진 업데이트 시 그 데이터에 대한 알림을 받는 서비스가 있을 때, 실시간으로 사진이 업데이트되면 newPhoto를 구독하고 있는 클라이언트에서는 ACTION이라는 카테고리의 사진이 올라올 때마다 newPhoto에 대한 데이터를 전달받게 된다.
스키마 문서화
GraphQL 스키마를 작성할 때 옵션으로 각 필드에 대한 설명을 적을 수 있다.
설명을 잘 작성해두면 본인뿐만 아니라 스키마를 사용하는 동료나 사용자에게도 큰 도움이 된다.
'Books' 카테고리의 다른 글
Naming Convention (0) | 2022.07.01 |
---|---|
[GraphQL] 3장 Graph 쿼리어 (0) | 2022.06.04 |
[GraphQL] 2장 그래프 이론 (0) | 2022.06.04 |
[GraphQL] 1장 GraphQL이란 (0) | 2022.06.02 |
[Clean Code] #13 동시성 (0) | 2022.05.12 |
- Total
- Today
- Yesterday
- redux-thunk
- React.memo
- Vue
- SOAP API
- reactrouter
- v-for
- Vuex
- 파이썬
- error
- GraphQL
- 문제풀이
- SPA
- python
- redux
- Vue.js
- 프로그래머스
- clean code
- js
- 알고리즘
- Transpiler
- React
- 상호평가
- TypeScript
- AxiosInterceptor
- webpack
- programmers
- Preloading
- Repository Pattern
- 백준
- bundler
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |