[번역] Schema-First GraphQL: The Road Less Travelled

원문 : https://blog.mirumee.com/schema-first-graphql-the-road-less-travelled-cf0e50d5ccff

개발자 커뮤니티는 특히 Python 생태계에서 코드 중복을 방지하면서 개발을 용이하게 하려는 시도와 함께 GraphQL API 개발에 대한 코드-우선 솔루션을 만들기 위해 상당한 노력을 투자했습니다. 일부는 이러한 과정을 발전의 일부분으로 간주하고 코드-우선 솔루션을 논리적 다음 단계로 인식합니다. 그러나 두 솔루션 모두 고유한 아키텍처 선택과 여러 소프트웨어의 옵션으로 연결됩니다. 아키텍처에서 스키마의 역할이 모든 프로젝트의 기본 변수와 배치 되는 것을 고려해야 합니다. 가능성을 검토 한 다음 스키마-우선 솔루션을 기본 솔루션으로 채책한 설득력 있는 사례를 알아보겠습니다.

The background: schema-first vs. code-first

만약 GraphQL 경험이 부족하다면, 여기서 빠르게 요약할 수 있습니다. GraphQL에서 API는 자신의 스키마 정의 언어를 통해 정의됩니다.

프로토콜 버퍼에 대한 경험이 있다면 익숙할 수 있습니다. 보시다시피 모든 기본 요소, 목록, 심지어 union과 인터페이스까지 포함하는 강력한 정적 타입 시스템이 함께 제공됩니다. GraphQL은 트리와 같은 구조를 설명하는 데 사용되는 것이 아니라 그래프 노드의 관계를 활성화하는데 사용되어야 한다는 점에 집중해야 합니다. 즉 모든 의미있는 엔티티 간의 연결을 사용하는 것이 좋으며 루트 노드가 없습니다.(GraphQL 로고 또한 트리가 아닙니다)

스키마-우선 솔루션에서 스키마를 SDL 언어로 작성한 다음 서버 애플리케이션에서 로드 하고 이를 내부 표현으로 파싱한 다음 resolver(필드 값을 제공하는 함수)를 필드에 매핑해야 합니다. 이 프로세스 중 일부는 수동으로 수행되며 모든 데이터 구조가 SDL 형식과 기본 애플리케이션 언어로 모두 표시되어야 합니다.

다음은 Ariadne 서버에서 하나의 간단한 예입니다.

from ariadne import ObjectType, QueryType, gql, make_executable_schema
from ariadne.asgi import GraphQL

type_defs = gql(
    """
    type Person {
        name: String
        house: House
        parents: [Person!]
        isAlive: Boolean
    }

    type House {
        name: String!
        castle: String!
        members: [Person!]
    }

    type Kingdom {
        ruler: Person
        kings: [Person!]
    }

    type Query {
        kingdoms: [Kingdom!]!
    }
"""
)


query = QueryType()


@query.field("kingdoms")
def resolve_kingdoms(*_):
    # this is where you would want to fetch and filter data you want
    house_of_stark = {"name": "Stark", "castle": "Winterfell"}
    king_in_the_north = {"name": "Jon Snow", "house": house_of_stark, "isAlive": True}
    return [{"ruler": king_in_the_north}]


schema = make_executable_schema(type_defs, [query])
app = GraphQL(schema)

로컬에서 실행할려 Ariadne와 Uvicorn을 설치하면 됩니다. 이제 간단한 명령으로 실행할 수 있습니다.

> uvicorn --reload got_server:app
INFO: Started server process [7586]
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

좋습니다! 현실에서 스키마를 별도의 schema.graphql 파일에 보관하거나 실행 가능한 스키마를 구축하는 동안 결합되고 유효성이 검사되는 여러 파일에 보관할 수 있습니다. 초기부터 Ariadne에서 스키마 모듈화를 활성화하기로 결정했지만 여기서 예제를 이해하기 더 어렵게 만들 뿐입니다.

GraphQL의 초강력 힘은 주로 클라이언트 측에서 원하는 데이터 필드와 모양을 정확하게 표현하는 능력에서 비롯됩니다. 다음은 기존 서버에 대한 샘플 쿼리입니다.

SDL은 놀랍게도 뒤처졌지만 이제는 GraphQL 스펙에서 공식 핵심 부분입니다. 그러나 개발자는 GraphQL 스키마가 네이티브 언어 추상화로 정의되고 리졸버에 직접 연결된 Graphene과 같은 프레임워크를 이미 만들었습니다. 애플리케이션이 실행되면 프레임워크는 이러한 추상화에서 스키마를 자동으로 컴파일합니다. 이 코드-우선 접근 방식은 graphene (Python), graphql-ruby, juniper (Rust), Prisma nexussangria (Scala)와 같은 라이브러리에서 구현됩니다.

Graphene에서 제시된 이전 예제는 다음과 비슷합니다.

import graphene


class House(graphene.ObjectType):
    name = graphene.String(required=True)
    castle = graphene.String(required=True)
    members = graphene.List(graphene.NonNull(lambda: Person))


class Person(graphene.ObjectType):
    name = graphene.String(required=True)
    house = graphene.Field(House)
    parents = graphene.List(graphene.NonNull(lambda: Person))
    is_alive = graphene.Boolean()


class Kingdom(graphene.ObjectType):
    name = graphene.String(required=True)
    castle = graphene.String(required=True)
    members = graphene.List(graphene.NonNull(Person))


class Query(graphene.ObjectType):
    kingdoms = graphene.NonNull(graphene.List(graphene.NonNull(Kingdom)))

    def resolve_kingdoms(self, info):
        house_of_stark = {"name": "Stark", "castle": "Winterfell"}
        king_in_the_north = {
            "name": "Jon Snow",
            "house": house_of_stark,
            "is_alive": True,
        }
        return [{"name": "The North", "ruler": king_in_the_north}]


schema = graphene.Schema(query=Query)

코드-우선 솔루션이 이미 사용중이라면 왜 수동으로 SDL을 작성할까요? 누가 하나 대신 두 가지 언어가 필요할까? 하지만 언뜻보기에도 간단하지 않습니다.

The case for schema-first development

우리는 그렇게 똑똑하지 않습니다.

작성중...

Last updated