본문 바로가기

배움의 즐거움/프로그래밍

(11) Graphql-ruby - Class-Based API v1.8.0

반응형





⚠ New Class-Based API ⚠

여기서는 v1.8.0 새로운 API 에 대해서 알아볼 것이다.


Class-based API


GraphQL 1.8+ 에서는 스키마를 빌드할 때 Ruby class를 사용할 수 있다. class 스타일과 define 스타일 정의 두 가지를 섞어서 스키마에 사용할 수 있다.


해당 버전의 새로운 특성들의 전반적인 사항들:


API에 대해서 배울 내용:


Rationale & Goals


이 새로운 API는 새로 시작하는 사람들과 스키마 커스터마이징에 좋은 경험을 주도록 개선하도록 노력하였다. 이를 위해 친근한 루비 구조(class & method) 과 함께 GraphQL-ruby DSL로 대체되었다.

추가적으로, 이 새로운 API는 현재 스키마 정의와 상호 보완적이므로 차차 사용될 수 있을 것이다.


Compatibility & Migration overview


사용자의 스키마 부분은 하나씩 대체될 수 있으므로 정의도 점점 바꿔나갈 수 있다.


Classes


일반적으로 .define { ... } 블락은 class로 변경될 것이다.

  • GraphQL::{X}Type 를 사용하는 대신에, GraphQL::Schema::{X} 예를 들면:

GraphQL::ObjectType.define { ... }  => GraphQL::Schema::Object

  • 자신의 앱에 base 클래스를 만드는 것을 추천하며, 그리고 나서 그 base 클래스를 각각의 타입에 상속하도록 한다. (rails 의 ApplicationController 같이,  Customizing Definitions 더보기)


스키마 정의 클래스의 구체적인 클래스는 아래에 볼 수 있다.


Type Instances


이전의 GraphQL::{X}Type 는 여전히 사용되기는 한다. 각각의 새로운 GraphQL::Schema::{X} 클래스는 일부 메서드를 구현한다.

  • .to_graphql:  GraphQL::{X}Type 의 새로운 인스턴스를 생성
  • .graphql_definitionGraphQL::{X}Type 의 캐쉬된 인스턴스를 리턴

만약 이 새로운 정의 스타일에 어긋나는 현재 자신의 코드가 있다면 내재된 타입 객체를 얻기 위해서는 graphql_definition 를 호출하면 된다. 


아래 설명된 것처럼, to_graphql 는 타입 시스템을 커스터마이징 하기 위해서 덮어쓸 수 있다.


List Types and Non-Null Types


이전에, 리스트 타입은  types[T] 로 표현되었고, non-null 타입은 types[T] 로 표현되었다. 이제는:


리스트 타입은 Ruby 배열 [T]로 표현된다. 예를 들어, field :owners, [Types::UserType]

  • 디폴트로는, 리스트의 값은 non-null 이다. 그러므로  [Types::UserType] 은 [User!] 이 된다.

  • 만약 리스트의 값이 null이 될 수 있다면 배열뒤에 null: true 를 추가하여 [Types::UserType, null: true] 가 [User] 이 된다.

field: project, Types::ProjectType, null: true


Non-null 타입은 키워드 argument인 null: 또는 required:로 표현된다.  

  • field 에서는 null: 키워드를 받는데,  null 이 될 수 있는 것은 null: true 로 표현되고, null 이 될 수 없는(!와 같은 뜻) 은 null: false로 표현할 수 있다.

  • argument 에서는 required: 키워드를 받는데, argument 가 non-null 이라면  required: true 로 표현되고, null 이 될 수 있다면  required: false로 표현된다.


레거시 스타일 클래스에서는 리스트와 non-null 타입을 만들기 위해서 Ruby 메서드를 사용했을 것이다.

  • #to_non_null_type 는 타입을 non-null로 변경한다 (예를 들어, T.to_non_null_type 은 !T 와 동일하다)
  • #to_list_type 는 타입을 리스트로 변환한다. (예를 들어, T.to_list_type 는 types[T]와 동일하다)



The !  메서드는 내장된 연산자와의 모호성을 피하기 위해서 제거됨 (field “someField” type must return GraphQL::BaseType, not FalseClass (false)  에러발생함)



호환을 위해,  ! 를 class-based 타입 정의에 backport 하고 싶을 것이다. 이를 위해서는 두 가지 방법이 있다:

A refinement, activated in file scope or class/module scope:

# 해당 스콥에서`!` 를사용할수 있게끔 함
using GraphQL::DeprecatedDSL

A monkeypatch, activated in global scope:

# 전체에서 `!` 를 사용 가능하게 함
GraphQL::DeprecatedDSL.activate


Connection fields & types


connection(...) 메서드는 존재하지 않는다. 대신에 connection field 가 타입 이름에서 참조된다. 만약에 타입 이름이 Connection 으로 끝난다면 해당 필드는 Connection 필드로서 다뤄진다. 이는 connection: true 또는 connection: false 키워드를 전달함으로써 덮어쓸 수 있다. 

# 타입 명이 "connection"으로 끝나므로 이것은 connection 으로 다뤄진다.
field :projects, Types::ProjectType.connection_type


Resolve function compatibility


만약 클래스를 사용하여 타입을 정의한다면, GraphQL-Ruby resolve 함수를 사용할 수 있다.

# Proc literal 이나 #call-able 사용
field :something, ... resolve: ->(obj, args, ctx) { ... }
# 미리 정의된 필드 사용
field :do_something, field: Mutations::DoSomething.field
# GraphQL::Function 사용
field :something, function: Functions::Something.new

resolve 함수로 구현할 때는(obj, args, ctx) 파라미터는 로 예전과 같다.


Upgrader


1.8 는 .define-based 신택스를 class-based 신택스로 바꾸기 위한 auto-upgrader 를 포함하고 있다. This upgrader is a pipeline of sequential transform operations.(?) 이것은 기본 파이프라인과 함께 제공되지만 내장된 파이프라인을 커스텀 된 것으로 교체함으로써 커스터마이징 하는 것도 가능하다.

이 upgrader 는 parser 에 의존성을 가지고 있는데, 프로젝트에 직접 추가해야 된다.(예를 들면 Gemfile 에 추가하는 것 처럼)

위의 두 가지 문법이 호환되므로 당신의 프로젝트에서 한번에 파일 한 개씩 변환될 것이다. 이렇게 함으로써, 몇개의 파일을 변환해본 뒤 테스트를 실행하여 문제점들을 확인하고 계속해서 진행할 수 있다.

이 변환은 완벽하지 않지만 대부분의 케이스들을 커버할 것이다. 더 질문이 있거나 버그에 대한 키포트는 여기서 하면 된다.


Using the Default Upgrade Task


The upgrader ships with rake tasks, included as a railtie (source). The railtie will be automatically installed by your Rails app, and it provides the following tasks:

upgrader 는 railtie (source) 라는 rake 태스크와 함께 온다. 이 railtie 는 자동적으로 너의 Rails 앱에 의해 설치되고 다음과 같은 태스크를 제공한다.

  • graphql:upgrade:schema[path/to/schema.rb]: 스키마 파일 업그레이드
  • graphql:upgrade:member[path/to/some/type.rb]: 타입 정의 업그레이드(객체, 인터페이스, 공용체 등등)
  • graphql:upgrade[app/graphql/**/*]:  _(type|interface|enum|union).rb 와 같은 접미사가 있는 파일에 대해 업그레이드 실행
  • graphql:upgrade:create_base_objects[path/to/graphql/]: 베이스 clase 들을 추가함


Writing a Custom Upgrade Task


커스텀 태스크가 다음과 같은 이유로 필요할 수도 있다.

  1. transformation pipeline 을 커스터마이징 하고 싶을 때
  2. Rails 를 사용하지 않기 때문에 railtie 가 동작하지 않을 때


커스텀 태스크를 작성하기 위해서 rake task 나 루비 스크립트를 작성하면 되는데, 이 때 업그레이드의 API를 직접 사용하면 된다.

아래를 보면 타입 정의를 업그레이드 하기 위한 기본 변형 파이프 라인의 코드를 볼 수 있다.

# 기존의 소스코드를 문자열로 읽음
original_source = File.read("path/to/type.rb")
# 기존 변형으로 업그레이더 초기화
upgrader = GraphQL::Upgrader::Member.new(original_source)
# 변형을 실행하고 변형된 소스 코드를 가져옴
transformed_source = upgrader.upgrade
# 새로운 코드로 해당 파일을 업데이트
File.write("path/to/type.rb", transformed_source)

커스텀 코드에서는 GraphQL::Upgrader::Member.new 에 키워드를 보내면 된다.


  • type_transforms: 소스코드 전체에 적용, 처음에 적용
  • field_transforms: 각각의 필드/커넥션/argument 정의에 적용
  • clean_up_transforms: 타입 또는 필드 변환 후 소스코드 전체에 적용


이 변형은 순서대로 진행된다. .unshift() 를 추가한다면 파이프라인의 맨 끝부분이 아니라 시작 부분에 변형을 추가할 수 있다.

For example, in script/graphql-upgrade:

#!/usr/bin/env ruby

# @example Upgrade app/graphql/types/user_type.rb:
#  script/graphql-upgrade app/graphql/types/user_type.rb

#기존의 define-to-class 변형을 커스텀 변형으로 대체
type_transforms = GraphQL::Upgrader::Member::DEFAULT_TYPE_TRANSFORMS.map { |t|
  if t == GraphQL::Upgrader::TypeDefineToClassTransform
    GraphQL::Upgrader::TypeDefineToClassTransform.new(base_class_pattern: "Platform::\\2s::Base")
  else
    t
  end
}

# 이 변형자를 리스트의 시작부분에 추가
type_transforms.unshift(GraphQL::Upgrader::ConfigurationToKwargTransform.new(kwarg: "visibility"))

# 업그레이더 실행
original_text = File.read(ARGV[0])
upgrader = GraphQL::Upgrader::Member.new(original_text, type_transforms: type_transforms)
transformed_text = upgrader.upgrade
File.write(filename, transformed_text)


Writing a custom transformer


변형 파이프라인에서 객체는 다음과 같다.

  • .new.apply(input_text) 에 응답하고 변형된 코드를 리턴
  • .apply(input_text) 에 응답하고 변형된 코드를 리턴


라이브러리는  GraphQL::Upgrader::Transform base class 와 함께 편리한 몇개의 메서드를 제공한다. 아래 제시된 내장 변형자들을 커스터마이징 하는것도 가능하다.


예를 들어 아래는 타입 정의를 model_type(model) do ... end  팩토리 메서드에서 class-based 신택스로 새롭게 작성한 것이다.

# `model_type` factory를 위핸 커스텀 변형 생성:
class ModelTypeToClassTransform < GraphQL::Upgrader::Transform
  def initialize
    # 내부에 타입 클래스가 있는 factory 메서드 호출 
    @find_pattern = /^( +)([a-zA-Z_0-9:]*) = model_type\(-> ?\{ ?:{0,2}([a-zA-Z_0-9:]*) ?\} ?\) do/
    # 클래스 정의로 변경
    @replace_pattern = "\\1class \\2 < Platform::Objects::Base\n\\1  model_name \"\\3\""
  end

  def apply(input_text)
    input_text.sub(@find_pattern, @replace_pattern)
  end
end
# 해당 클래스를 파이프라인 시작에 추가
type_transforms.unshift(ModelTypeToClassTransform)


Roadmap

이 기능을 구현하기 위한 계획은 다음과 같다.


- 진행중:

  • ☐ 새로운 API 에 대해 피드백 받기 (사용성 & 목적)

    - graphql 1.8:
    • ☑ 싱글톤 대신 클래스에 기초한 스키마 정의 API 빌드
    • ☑ GitHub의 GraphQL 스키마의 일부를 새 API에 마이그레이션
    • ☑ 고급 클래스 기반 features 빌드:

      • ☑ 커스텀 Context 클래스
      • ☑ 커스텀 introspection 타입
      • ☐ Custom directives 현재는 투가가치 없음
      • ☐ Custom Schema#execute method 필요없음
    • ☑ GitHub’s GraphQL schema 를 모두 새 API 에 마이그레이션

- graphql 1.9:

    • ☐  새 API를 반영하도록 모든 GraphQL-Ruby docs 업데이트


- graphql 1.10:

  • .define:  격리


graphql 2.0:

  • ☐ .define:  제거


Common Type Configurations

몇몇의 설정은 아래와 같이 모든 타입에 사용 가능하다.

  • graphql_name 는 타입 name 을 오버라이드 함overrides the type name. (디폴트 값은 네임스페이스를 제외한 루비 상수 이름, 예를들어 Types::TodoList  일 경우에 디폴트 네임은 TodoList) 
  • description 은 GraphQL introspection을 위한 설명을 제공


예를 들면:

class Types::TodoList < GraphQL::Schema::Object # 또는 Scalar, Enum, Union, 뭐든지 간에
  graphql_name "List" # 디폴트 값"TodoList" 을 오버라이드
  description "해야 할 일들의 리스트"
end


* 해당 글은 번역기 돌리다가 크롬 번역기 말도 안되는 해석에 지친 본인이 나중에 참고할 의도로 대충대충 발로 해석한 것이니 참고용으로만 사용하시길 바랍니다.

* 출처: http://graphql-ruby.org/schema/class_based_api.htm


반응형