Hun's Blog

Nexus GraphQL tutorials (2) - Schema란? / Writing your first schema 본문

Backend/GraphQL

Nexus GraphQL tutorials (2) - Schema란? / Writing your first schema

jhk-im 2020. 11. 30. 20:30

GraphQL을 개인 프로젝트에 적용하기위해 제대로 학습해보기 시리즈

해당 글은 Nexus 를 개인 프로젝트에 적용하고자 제대로 학습하기 위해 Nexus tutorial의 내용을 정리한 것입니다.

Nexus tutorial에도 상세하게 나와있음을 알려드립니다. 

Schema

컴퓨터 과학에서 데이터베이스 스키마는 데이터베이스의 자료구조, 자료의 표현방법, 자료 간의 관계를 형식 언어로 정의한 구조이다. 데이터베이스 관리 시스템(DBMS)이 주어진 설정에 따라 데이터베이스 스키마를 생성하며, 데이터베이스 사용자가 자료를 저장, 조회, 삭제, 변경할 때 DBMS는 자신이 생성한 데이터베이스 스키마를 참조하여 명령을 수행한다. 

  • 외부스키마(External Schema): 프로그래머나 사용자의 입장에서 데이터베이스의 모습으로 조직의 일부를 정의한 것
  • 개념스키마(Conceptual Schema): 모든 응용 시스템과 사용자들이 필요로하는 데이터를 통합한 조직 전체의 데티어베이스 구조를 논리적으로 정의한 것 
  • 내부스키마(Internal Schema): 전체 데이터베이스의 물리적 저장 형태를 기술하는 것 

-위키백과-

 

데이터 베이스의 구조와 제약 조건에 관해 전반적인 내용을 기술 한 것 

객체의 속성(Attribute), 속성의 집합(Entity), 객체 사이에 존재하는 관계(Relation)에 대한 정의, 이 모든것들이 유지해야 할 제약조건 기술 -> 즉 데이터가 어떠한 구조로 저장되는가를 나타내는 데이터베이스 구조를 스키마라고 한다. 

 

스키마에 대한 자세한 설명

 

[DB기초] 스키마란 무엇인가?

 스키마란? 1. 스키마는 데이터베이스의 구조와 제약 조건에 관한 전반적인 명세를 기술한 메타데이터의 집합이다. 2. 스키마는 데이터베이스를 구성하는 데이터 개체(Entity), 속성(Attribute), 관계

coding-factory.tistory.com

Writing your first schema

 

2. Writing your first schema

2. Writing your first schema

nexusjs.org

  • GraphQL 객체 작성
  • Query operations에 대한 GraphQL 객체노출
  • GrpahQL SDL 파일 생성
  • 향상된 type-safe와 자동완성

Reflection?

Nexus 개발 작업흐름의 중요한 컨셉

maseSchema()가 호출 될 때 어플리케이션 코드가 실행될 뿐만 아니라 정보가 수집되고, artifacts가 파생되고 있다는 사실을 가리킨다. 

  • Resolver에 온전한 type-safe를 제공하기 위한 TypeScript 타입을 생성
  • SDL 파일 생성 

이는 Nexus가 선언적 API를 가지고있는 이유를 부분적으로 설명한다. 빌드타임에 앱을 안정적으로 실행할 수 있는 방법이 필요하며 선언적 API는 Neuxs에 더욱 높은 수준의 제어력을 제공한다. 

 

ts-node-dev에 전달되는 --transfile-only 플래그에 대한 필요성

--transfile-only는 기본적으로 TypeScript에게 앱을 타이핑하지 말라고 말한다. TypeScript 타입을 생성하려면 @nexus/schema를 실행해야 하기 때문에 @nexus/schema 가 이러한 타입을 생성하지 못하도록 해야한다. 

 

서버를 사용하지 않는 경우에도 npm run dev 스크립트를 실행

npm run dev를 실행하지 않으면 resolver에서 요구하는 static typing 경험을 얻을 수 없다.  

 

Model the domain

튜토리얼은 Post 컨셉으로 시작한다. post는 블로그의 한 페이지를 나타내며, 아직 공개되지 않는 경우에는 draft라고 부를 것이다. 

모델링 작업은 데이터베이스와 반대로 API 계층에서 시작된다. 이러한 API 우선 접근방식은 frontend 팀과 협업하여 데이터를 초기에 형성하는데 필요한 정보를 얻는 좋은 방법이다. 

 

API 계층에는 객체가 있지만 데이터베이스 계층에는 모델이 있다. 이러한 이름의 차이는 서로다른 계층에 대한 혼동을 줄여준다. 또한 GraphQL(API 계층) / Prisma(Database 계층)에서 각각의 객체를 가리키는 방법이다. 

 

해당 튜토리얼에선 modular style을 사용한다. 

mkdir api/graphql && touch api/graphql/Post.ts
// api/graphql/Post.ts
import { objectType } from '@nexus/schema'
export const Post = objectType({
  name: 'Post',            // <- Name of your type
  definition(t) {
    t.int('id')            // <- Field named `id` of type `Int`
    t.string('title')      // <- Field named `title` of type `String`
    t.string('body')       // <- Field named `body` of type `String`
    t.boolean('published') // <- Field named `published` of type `Boolean`
  },
})
// api/graphql/index.ts
export * from './Post'

@nexus/schema 의 objectType 함수를 사용하여 Post 객체를 생성한다. 

또한 makeSchema 함수에 전달해야하므로 api/graphql/index.ts 파일을 만들어 스키마에서 모든 유형을 내보내는 index로 사용한다. 

 

마지막으로 Post 객체를 스키마로 만들기 위해 makeSchema가 있는 api/schema.ts로 전달한다. 

// api/schema.ts
 import { join } from 'path'
 import * as typeDefs from './graphql'
 const schema = makeSchema({
  types: typeDefs,
   outputs: {
     typegen: join(__dirname, '..', 'nexus-typegen.ts'),
     schema: join(__dirname, '..', 'schema.graphql')
   }
 })

참고 : import * 에서 곧바로 타입을 전달하는 것이 최적의 사례로 간주된다. 이렇게하면 API의 모든 유형을 수동으로 내보내고 가져올 필요가 없게된다. 

 

SDL?

이제 프로젝트 루트에 schema.graphql 파일이 생성된것을 확인할 수 있다. 

여기에는 SDL 구문으로 스키마를 표현하는 내용이 포함되어있다. 

개발 모드에서 Nexus는 앱을 재시작할 때 마다 이를 생성한다. 

type Post {
  id: Int
  title: String
  body: String
  published: Boolean
}

해당 파일을 비활성화할 수있지만 이 파일의 존재는 다음의 두가지 이점이 있다. 

 

1. SDL에 익숙한 사용자는 Nexus의 스키마 API를 빨리 이해하는데 도움이 된다. 

2. SDL 구문은 넥서스 혹은 자바스크립트에 대해 모르더라도 들어오는 API의 변화를 평가할 수 있는 이해하기 쉬운 방법이다. 

 

 

Your first home-grown query

현재 Post오브젝트는 클라이언트가 데이터를 읽을 방법이 없다. Query 객체를 활용하여 Post 객체를 노출하도록 하겠다. 

우선 API 클라이언트가 블로그 draft를 읽을 수 있도록해보자. 

 

// api/graphql/Post.ts                   // 1
import { objectType, extendType } from '@nexus/schema'
export const PostQuery = extendType({
  type: 'Query',                         // 2
  definition(t) {
    t.nonNull.list.field('drafts', {     // 3, 4, 5
      type: 'Post',                      // 6, 7
    })
  },
})
type Query {
  drafts: [Post!]!
}
  1. 이전과 일관성을 유지하여 모듈화 방식을 택하여 진행한다. Query를 분리시키지않고 Post에서 객체의 노출을 취합하는 방식이 모듈화 방식이다. 
  2. Nexus에서 colloaction을 달성하기 위해 schema.extendType을 사용할 것이다. 정의된 필드가 targeted type으로 병합되는 차이점을 가진 schema.object와 매우 유사하다.
  3. 기본적으로 Nexus에서는 모든 출력 타입(필드별로 반환되는..)은 null이 가능하다.  위 경우 .nonNull은 리스트가 항상 반환되고 절대 null이 될수 없다는 의미이다. Nexus의 default는 바꿀 수 있다.
  4. .list는 필드 타입 사양을 확장하여 list 타입으로 포장한다. 
  5. 첫번째 매개변수는 필드이름을 지정한다. => 'draft'
  6. type:은 'Post' 필드타입을 지정한다.
  7. Nexus는 다음과 같이 타입필드에서 list와 nullability를 지정할 수 있다. 
t.field('drafts', {
  type: nonNull(list('Post')),
})

이제 resolver property가 누락되었다는 IDE의 피드백을 보게 될 것이다. 

다음을 추가해 보자. 

// api/graphql/Post.ts
import { extendType } from '@nexus/schema'
// ...
export const PostQuery = extendType({
  type: 'Query',
  definition(t) {
    t.nonNull.list.field('drafts', {
      type: 'Post',
      // 추가
      resolve() {
        return [{ id: 1, title: 'Nexus', body: '...', published: false }]
      },
    })
  },
})

Try it out

GraphQL playground를 열고 다음의 query를 실행해보자. 

{
  drafts {
    id
    title
    body
    published
  }
}

아래와 같은 결과를 보여준다. 

{
  "data": {
    "posts": [
      {
        "id": 1,
        "title": "Nexus",
        "body": "...",
        "published": false
      }
    ]
  }
}