본문 바로가기

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

(24) Graphql-ruby - 인터페이스

반응형




Interfaces



인터페이스란 객체타입에 의해 구현될 수 있는 필드의 집합이다.


인터페이스는 필드를 가지고 있지만 실제로 인스턴스화 되지는 않는다. 대신 객체가 인터페이스를 구현하는데 이때 해당 객체는 인터페이스의 멤버가 된다. 또한 필드는 인터페이스 타입을 리턴할 수도 있다. 이 때 리턴된 객체는 해당 그 인터페이스의 멤버일 수 있다.


예를 들어, Customer (인터페이스) 가 Individual (객체) 또는 a Company (객체) 둘 중 하나라고 보자. 

interface Customer {
  name: String!
  outstandingBalance: Int!
}

type Company implements Customer {
  employees: [Individual!]!
  name: String!
  outstandingBalance: Int!
}

type Individual implements Customer {
  company: Company
  name: String!
  outstandingBalance: Int!
}

Customer 인터페이스는 두가지 필드를 필요로 한다. name: String! 과 outstandingBalance: Int! 이다. Company 와 Individual둘 다 해당 필드들을 구현하였으므로 이 둘은 Customer를 구현할 수 있다. Customer의 구현은 각각의 정의에 implements Customer를 추가함으로써 이행된다.


쿼리를 할 때, 인터페이스에 있는 필드들을 가져올 수 있다.

{
  customers(first: 5) {
    name
    outstandingBalance
  }
}


객체가  Company 이던 Individual 던 상관 없다. 여전히 name 과 outstandingBalance 필드를 가져올 수 있다. 만약 특정한 객체를 가져오고 싶다면 inline fragment를 이용하여 쿼리를 하면 된다. 예를 들어:

{
  customers(first: 5) {
    name
    ... on Individual {
      company { name }
    }
  }
}

이 뜻은 customer 이 Individual 일때는 회사의 이름도 함께 가져오라는 것을 뜻한다.

인터페이스는 객체의 일부분이 호환성있게 사용될 때에, 그리고 그들이 많은 필드를 공통으로 가지고 있을 때 아주 좋은 선택이 될 수 있다. 만약 공통으로 가지고 있는 필드가 없다면 Union 을 쓰는게 더 적합하다.


Defining Interface Types


인터페이스는 GraphQL::Schema::Interface를 포함하는 Ruby 모듈이다. 먼저 베이스 모듈을 만들자.

module Types::BaseInterface
  include GraphQL::Schema::Interface
end


그리고 베이스 인터페이스를 각 인터페이스에 포함한다.

module Types::RetailItem
  include Types::BaseInterface
  description "Something that can be bought"
  field :price, Types::Price, "How much this item costs", null: false

  def price
    # 옵션: 여기에 price 구현 
  end


  definition_methods do
    # 옵션: 만약 이 메서드가 정의되어 있다면 이것은 `Schema.resolve_type` 를 오버라이드 한 것
    def resolve_type(object, context)
      # ...
    end
  end
end

 

인터페이스 클래스는 절대로 인스턴스화 되지 않는다. 런타임때에 오직 .resolve_type 메서드만 불린다.(만약 이게 정의되어 있다면)


Implementing Variant Types


인터페이스를 구현하는 타입을 정의하기 위해서는 implements 메서드를 사용하면 된다.
class Types::Car < Types::BaseObject
  implements Types::RetailObject

  # ... 추가적인 필드
end

class Types::Purse < Types::BaseObject
  implements Types::RetailObject

  # ... 추가적인 필드
end


Implementing Fields


인터페이스는 signature과 함께 필드 구현을 제공할 수도 있다.(Interfaces may provide field implementations along with the signatures.)  예를 들어:

field :price, Types::Price, "How much this item costs", null: false

# 해당 필드가 `::Price` object 를 리턴하도록 구현
def price
  ::Price.from_cents(@object.price_in_cents)
end

이 메서드는 해당 인터페이스를 구현하는 객체에 의해 호출될 것이다.  객체 클래스에서 이 price 메서드 부분을 오버라이드 하는 것은 가능하다.

더 자세하 내용은 Fields guide서 보자.


Definition Methods


인터페이스 모듈에 definition_methods do ... end 를 추가하여 헬퍼 메서드를 추가할 수 있다.

  • 해당 메서드는 인터페이스 자체에서도 클래스 메서드로 사용할 수 있다.
  • 이 클래스 메서드는 해당 인터페이스를 include 하는 인터페이스에서도 사용될 수 있다.


이러한 방법으로 클래스 메서드들은 해당 인터페이스를 include  하는 다른 인터페이스들에 상속될 수 있다.( definition_methods는 ActiveSupport::Concern의 class_methods와 같다. 그러나 이 것과의 충돌을 피하기 위해 다른 이름을 가지고 있는 것이다.)

베이스 인터페이스에 헬퍼 정의에 추가할 수 있고 이후 구체적인 인터페이스에서 이를 이용할 수 있다.

# 우선, 헬퍼 메서드 `BaseInterface`의 정의 메서드에 추가한다.
module Types::BaseInterface
  include GraphQL::Schema::Interface

  definition_methods do
    # 이 메서드를 price 필드를 추가하고 디폴트 구현을 위해 사용해라
    def price_field
      field(:price, ::Types::Price, null: false)
      define_method(:price) do
        ::Price.from_cents(@object.price_in_cents)
      end
    end
  end
end

# 그리고 이후에 호출
module Types::ForSale
  include Types::BaseInterface
  # 이것은 definition methods 로부터 `price_field` 를 호출한다
  price_field
end


Resolve Type


필드가 리턴하는 것의 타입이 인터페이스 일 경우 GraphQL은 리턴 값으로 어떠한 구체적인 객체 타입을 사용해야 되는지 알아내야 한다. 위의 예시에서 보면 각customer 은 반드시 Individual 또는 Company 로 분류된다. 이건 다음와 같이 할 수 있다.

  • top-level Schema.resolve_type 메서드 제공하거나 또는
  • definition_methods 안에서 interface-level .resolve_type 메서드 제공 



이 메서드는 객체가 반드시 명확해야 할 때마다 호출된다.

module Types::RetailItem
  include Types::BaseInterface
  definition_methods do
    # `object` 에 대해 어떠한 객체타입을 사용해야 되는지 결정한다
    def resolve_type(object, context)
      if object.is_a?(::Car) || object.is_a?(::Truck)
        Types::Car
      elsif object.is_a?(::Purse)
        Types::Purse
      else
        raise "Unexpected RetailItem: #{object.inspect}"
      end
    end
  end
end


Orphan Types


만약 인터페이스를 구현하는 객체 타입을 추가하였을 때,그런데 그 객체 타입이 당신의 스키마에 적당히 나타나지 않을 때, 해당 객체를 인터페이스의 orphan_types에 추가할 수 있다.

module Types::RetailItem
  include Types::BaseInterface
  # ...
  orphan_types Types::Comment
end


대안으로, 스키마의 orphan_types 에 그 객체타입을 추가할 수 있다.

class MySchema < GraphQL::Schema
  orphan_types Types::Comment
end


This is required because a schema finds it types by traversing its fields, starting with query, mutation and subscription. 

만약 객체가 인터페이스를 통해서만 연결되어 있고 필드의 리턴타입이 아니라면, orphan_types을 통해서 명시적으로 스키마에 연결되어 있어야 한다. 

type Query {
  node(id: ID!): Node
}

interface Node {
  id: ID!
}

type Comment implements Node {
  id: ID!
}

Comment 는 반드시 orphan_types을 통해서 추가되어야 하는데 이것은 필드의 리턴타입으로는 쓰이지 않기 때문이다.(오직  Node 와 ID  만이 리턴타입으로 쓰임) - (?)


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

* 출처:http://graphql-ruby.org/type_definitions/interfaces.html




반응형

'배움의 즐거움 > 프로그래밍' 카테고리의 다른 글

(26) Graphql-ruby - 리스트  (0) 2019.01.01
(25) Graphql-ruby - 유니언  (0) 2019.01.01
(23) Graphql-ruby - input 객체  (0) 2019.01.01
(22) Graphql-ruby - enums  (0) 2019.01.01
(21) Graphql-ruby - 스칼라  (0) 2019.01.01