DGS generate code from schema

업데이트: Link

코드 생성

DGS 코드 생성 플러그인은 도메인 그래프 서비스의 GraphQL 스키마 파일을 기반으로 프로젝트 빌드 프로세스 중에 코드를 생성합니다. 플러그인은 다음을 생성합니다.

  • 유형, 입력 유형, 열거형 및 인터페이스에 대한 데이터 유형.
  • DgsConstants유형 및 필드 이름을 포함 하는 클래스
  • 데이터 가져오기 예제
  • 쿼리를 나타내는 유형 안전 쿼리 API

빠른 시작

코드 생성은 일반적으로 빌드에 통합됩니다. Gradle 플러그인은 항상 사용 가능했으며 최근에는 커뮤니티 에서 Maven 플러그인을 사용할 수 있게 되었습니다.

플러그인을 적용하려면 다음을 포함하도록 프로젝트 build.gradle파일을 업데이트하십시오.

// Using plugins DSL
plugins {
  id  "com.netflix.dgs.codegen"  version  "[REPLACE_WITH_CODEGEN_PLUGIN_VERSION]"
}

또는 buildscript에서 클래스 경로 종속성을 설정할 수 있습니다.

buildscript {
  dependencies {
    classpath  'com.netflix.graphql.dgs.codegen:graphql-dgs-codegen-gradle:[REPLACE_WITH_CODEGEN_PLUGIN_VERSION]'
  }
}
apply  plugin:  'com.netflix.dgs.codegen'

다음으로 다음과 같이 작업 구성을 추가해야 합니다.

generateJava {
   schemaPaths = ["${projectDir}/src/main/resources/schema"] // List of directories containing schema files
   packageName = 'com.example.packagename' // The package name to use to generate sources
   generateClient = true // Enable generating the type safe query API
}

:memo: Info

여기 에서 제공되는 최신 버전의 플러그인을 사용 하십시오.

플러그인 generateJava은 프로젝트 빌드의 일부로 실행되는 Gradle 작업을 추가합니다. generateJava는 프로젝트 build/generated 디렉토리에 코드를 생성합니다. Kotlin 프로젝트에서 이 generateJava 작업은 기본적으로 Kotlin 코드를 생성합니다(예, 이름이 혼동스럽습니다). 이 폴더는 프로젝트의 클래스 경로에 자동으로 추가됩니다. 유형은 에서 지정한 패키지의 일부로 사용할 수 있습니다 . 여기서 packageNamepackageName.types 값을 build.gradle 파일 의 구성으로 지정 합니다. 프로젝트의 소스 가 지정된 패키지 이름을 사용하여 생성된 코드를 참조하는지 확인하십시오.

generateJava데이터 페처를 생성하고 build/generated-examples에 배치합니다 .

:memo: Info

generateJava는 생성하는 데이터 가져오기 프로그램을 프로젝트 소스에 추가하지 않습니다. 이러한 페처는 주로 추가 구현이 필요한 기본 상용구 코드 역할을 합니다.

플러그인 schemaPaths의 일부로 지정되지 않은 다른 스키마 디렉토리에 배치하여 코드 생성에서 스키마의 일부를 제외할 수 있습니다.

“Could not initialize class graphql.parser.antlr.GraphqlLexer” 문제 수정

Gradle의 플러그인 시스템은 모든 플러그인에 플랫 클래스 경로를 사용하므로 클래스 경로 충돌이 매우 쉽게 발생합니다. Codegen 플러그인의 종속성 중 하나는 ANTLR이며, 불행히도 일부 다른 플러그인에서도 사용됩니다. 이와 같은 Could not initialize class graphql.parser.antlr.GraphqlLexer 오류가 표시되면 일반적으로 클래스 경로 충돌을 나타냅니다. 이 경우 빌드 스크립트에서 플러그인 순서를 변경하십시오. ANTLR은 일반적으로 이전 버전과 호환되지만 이전 버전과는 호환되지 않습니다.

다중 모듈 프로젝트의 경우 적용하지 않고 루트 빌드 파일에서 Codegen 플러그인을 선언해야 함을 의미합니다.

plugins {
    id("com.netflix.dgs.codegen") version "[REPLACE_WITH_CODEGEN_PLUGIN_VERSION]" apply false

    //other plugins
}

플러그인을 적용해야 하는 모듈에서 plugins 블록에 플러그인을 다시 지정하지만 버전은 지정하지 않습니다.

plugins {
    id("com.netflix.dgs.codegen")
}

오래된 buildscript 구문을 사용하는 경우 플러그인 종속성을 root buildscript에 추가 하지만 모듈 apply에만 추가합니다.

JAR의 외부 스키마에서 코드 생성

dgsCodegen 구성에서 종속성으로 선언하여 생성에 사용할 스키마를 포함하는 외부 종속성을 지정할 수도 있습니다. 플러그인은 모든 .graphql, .graphqls 파일을 스캔 하고 동일한 디렉토리 build/generated에서 해당 클래스를 생성합니다. 이는 코드 생성을 위해 스키마에 추가하려는 일부 공유 유형을 포함하는 외부 종속성이 있는 경우에 유용합니다. 이것은 프로젝트의 스키마에 영향을 미치지 않으며 코드 생성에만 해당됩니다.

dependencies {
    // other dependencies
    dgsCodegen 'com.netflix.graphql.dgs:example-schema:x.x.x'
}

기존 유형 매핑

Codegen은 몇 가지 예외를 제외하고 스키마에서 찾은 각 유형에 대한 유형을 생성하려고 시도합니다.

  1. 기본 스칼라 유형 - 해당 Java/Kotlin 유형(문자열, 정수 등)에 매핑됩니다.
  2. 날짜 및 시간 유형 - 해당 java.time클래스 에 매핑됩니다.
  3. PageInfo 및 RelayPageInfo - graphql.relay클래스 에 매핑됨
  4. typeMapping구성 으로 매핑된 유형

특정 유형에 대한 클래스를 생성하는 대신 사용하려는 기존 클래스가 있는 경우 typeMapping을 사용하여 플러그인을 구성할 수 있습니다. typeMapping 구성은 각 키가 GraphQL 유형이고 각 값이 정규화된 Java/Kotlin 유형인 Map입니다.

generateJava{
   typeMapping = ["MyGraphQLType": "com.mypackage.MyJavaType"]
}

클라이언트 API 생성

코드 생성기는 클라이언트 API 클래스를 생성할 수도 있습니다. 이러한 클래스를 사용하여 Java를 사용하는 GraphQL 엔드포인트 또는 QueryExecutor를 사용하는 단위 테스트에서 데이터를 쿼리할 수 있습니다. Java GraphQL 클라이언트는 서버 간 통신에 유용합니다. GraphQL 자바 클라이언트는 프레임워크의 일부로 사용 가능합니다.

코드 생성은 각 Query 및 Mutation 필드에 대해 field-nameGraphQLQuery를 생성합니다. *GraphQLQuery 쿼리 클래스에는 필드의 각 매개변수에 대한 필드가 포함되어 있습니다. Query 또는 Mutation에서 반환된 각 유형에 대해 코드 생성은 *ProjectionRoot를 생성합니다. 프로젝션은 반환되는 필드를 지정하는 빌더 클래스입니다.

다음은 생성된 API의 사용 예입니다.

GraphQLQueryRequest graphQLQueryRequest =
        new GraphQLQueryRequest(
            new TicksGraphQLQuery.Builder()
                .first(first)
                .after(after)
                .build(),
            new TicksConnectionProjection()
                .edges()
                    .node()
                        .date()
                        .route()
                            .name()
                            .votes()
                                .starRating()
                                .parent()
                            .grade());

이 API는 다음 스키마를 기반으로 생성되었습니다. edgesnode유형은 스키마가 페이지 매김을 사용하기 때문입니다. API는 문자열로 쿼리를 작성하는 것과 거의 동일한 느낌으로 유창한 쿼리 작성 스타일을 허용하지만 코드 완성 및 유형 안전성의 추가 이점이 있습니다.

type Query @extends {
    ticks(first: Int, after: Int, allowCached: Boolean): TicksConnection
}

type Tick {
    id: ID
    route: Route
    date: LocalDate
    userStars: Int
    userRating: String
    leadStyle: LeadStyle
    comments: String
}

type Votes {
    starRating: Float
    nrOfVotes: Int
}

type Route {
    routeId: ID
    name: String
    grade: String
    style: Style
    pitches: Int
    votes: Votes
    location: [String]
}

type TicksConnection {
    edges: [TickEdge]
}

type TickEdge {
    cursor: String!
    node: Tick
}

외부 서비스용 쿼리 API 생성

위와 같이 Query API를 생성하는 것은 자신의 DGS를 테스트하는 데 매우 유용합니다. 동일한 유형의 API는 코드가 해당 서비스의 클라이언트인 다른 GraphQL 서비스와 상호 작용할 때도 유용할 수 있습니다. 이는 일반적으로 DGS 클라이언트 를 사용하여 수행됩니다 .

자체 스키마와 내부 스키마 모두에 대해 코드 생성을 사용하는 경우 둘 다에 대해 서로 다른 코드 생성 구성을 원할 수 있습니다. 권장 사항은 Query API를 생성하기 위해 외부 서비스의 스키마와 codegen 구성을 포함하는 별도의 모듈을 프로젝트에 생성하는 것입니다.

다음은 Query API 만 생성하는 구성 예시입니다.

generateJava {
    schemaPaths = ["${projectDir}/composed-schema.graphqls"]
    packageName = "some.other.service"
    generateClient = true
    generateDataTypes = false
    skipEntityQueries = true
    includeQueries = ["hello"]
    includeMutations = [""]
    shortProjectionNames = true
    maxProjectionDepth = 2
}

클라이언트 API에 대해 생성된 코드 제한

스키마가 크거나 주기가 많은 경우 전체 스키마에 대한 클라이언트 API를 생성하는 것은 이상적이지 않습니다. 많은 수의 프로젝션이 발생하기 때문입니다. 이로 인해 스키마에 따라 코드 생성 속도가 크게 느려지거나 메모리가 부족해질 수 있습니다. 이를 조정하는 데 도움이 되는 몇 가지 구성 매개변수가 있으므로 클라이언트 API 생성을 필요한 항목으로만 제한할 수 있습니다.

generateJava {
    ...
    generateClient = true
    skipEntityQueries = true
    includeQueries = ["hello"]
    includeMutations = [""]
    includeSubscriptions = [""]
    maxProjectionDepth = 2
}

첫째, includeQueries, includeMutationsincludeSubscriptions를 통해 생성할 쿼리/변형/구독을 정확히 지정할 수 있습니다. skipEntityQueries는 테스트 목적으로 통합 _entities 쿼리를 구성하는 경우에만 사용되므로 생성된 코드의 양을 제한하도록 설정할 수도 있습니다. 마지막으로 maxProjectionDepth는 쿼리 루트에서 그래프의 2레벨 이상 생성을 중지하도록 codegen에 지시합니다. 기본값은 10입니다. 이렇게 하면 투영 수를 추가로 제한하는 데 도움이 됩니다.

사용자 지정 주석으로 클래스 생성

이 기능은 graphQL에서 @annotate 지시문을 사용하여 생성된 POJO에서 사용자 정의 주석을 지원하는 기능을 제공합니다. @annotate지시문은 graphQL의 유형, 입력 또는 필드에 배치할 수 있습니다. 이 기능은 기본적으로 꺼져 있으며 build.gradle에서 generateCustomAnnotationtrue로 설정하여 활성화할 수 있습니다.

generateJava {
    ...
    generateCustomAnnotations = true
}

@annotate에는 4개의 필드가 있습니다.

  • name - 필수 필드입니다. 주석의 이름입니다. 예: ValidPerson. 주석 이름과 함께 패키지를 가질 수 있습니다. 예: com.test.ValidPerson. 주석 이름과 함께 제공된 패키지 값은 build.gradle의 매핑된 패키지보다 우선합니다.
  • type - 선택적 필드입니다. 이 변수는 build.gradle에서 주석 패키지를 매핑하는 데 사용됩니다. 주석 이름이 지정된 경우 패키지가 이 값보다 우선합니다. 그러나 둘 다 제공되지 않으면 빈 문자열이 사용됩니다.
  • inputs - 선택적 필드입니다. 키-값 쌍의 주석에 대한 입력을 포함합니다. 예: inputs: {types: [HUSBAND, WIFE]}. 입력은 String, int, float, enums, list, map, class 등의 유형일 수 있습니다. 클래스 입력에 대해서는 클래스 객체의 예를 참조하십시오.
  • target - 선택적 필드입니다. 주석의 사이트 대상을 나타냅니다. 대상 사이트에서 사용 가능한 값은 대상 사이트 문서 사용 을 참조하십시오.

graphQL의 @annotate 정의:

"Custom Annotation"
directive @annotate(
    name: String!
    type: String
    inputs: JSON
    target: String
) repeatable on OBJECT | FIELD_DEFINITION | INPUT_OBJECT | INPUT_FIELD_DEFINITION

스키마에 지정된 사용자 지정 주석은 런타임 오류를 방지하기 위해 확인자에 의해 해당 구현이 필요합니다.

몇 가지 예:

type Person @annotate(name: "ValidPerson", type: "validator", inputs: {types: [HUSBAND, WIFE]}) {
       name: String @annotate(name: "com.test.anotherValidator.ValidName")
       type: String @annotate(name: "ValidType", type: "personType", inputs: {types: [PRIMARY, SECONDARY]}) 
}

주석 및 열거형에 대한 패키지 매핑은 build.gradle 파일에서 제공될 수 있습니다.

generateJava {
    ...
    generateCustomAnnotations = true
    includeImports = ["validator": "com.test.validator"]
    includeEnumImports = ["ValidPerson": ["types": "com.enums"]]
}

Java에서 POJO를 생성했습니다. 이 기능은 Kotlin에서도 사용할 수 있습니다.

package com.netflix.graphql.dgs.codegen.tests.generated.types;

import com.test.anotherValidator.ValidName;
import com.test.validator.ValidPerson;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;

@ValidPerson(
    types = [com.enums.HUSBAND, com.enums.WIFE]
)
public class Person {
  @ValidName
  private String name;

  @ValidType(
      types = [com.personType.enum.PRIMARY, com.personType.enum.SECONDARY]
  )
  private String type;

  public Person() {
  }

  public Person(String name, String type) {
    this.name = name;
    this.type = type;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

  @Override
  public String toString() {
    return "Person{" + "name='" + name + "'," +"type='" + type + "'" +"}";
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person that = (Person) o;
        return java.util.Objects.equals(name, that.name) &&
                            java.util.Objects.equals(type, that.type);
  }

  @Override
  public int hashCode() {
    return java.util.Objects.hash(name, type);
  }
}

클래스 개체의 예:

GraphQL 파서에는 클래스 객체에 대한 기본 제공 지원이 없으므로 클래스는 스키마에서 “.class”로 끝나는 문자열로 표시됩니다.

type Person @annotate(name: "ValidPerson", type: "validator", inputs: {groups: "BasicValidation.class"}) {
    name: String @annotate(name: "com.test.anotherValidator.ValidName")
}

주석 및 클래스에 대한 패키지 매핑은 build.gradle 파일에서 제공할 수 있습니다. 매핑이 제공되지 않으면 입력이 문자열로 처리됩니다.

generateJava {
    ...
    generateCustomAnnotations = true,
    includeImports = mapOf(Pair("validator", "com.test.validator")),
    includeClassImports = mapOf("ValidPerson" to mapOf(Pair("BasicValidation", "com.test.validator.groups")))
}

Java에서 POJO를 생성했습니다. 참고: Kotlin에서 위와 동일한 스키마를 사용하면 BasicValidation::class가 생성됩니다.

package com.netflix.graphql.dgs.codegen.tests.generated.types;

import com.test.anotherValidator.ValidName;
import com.test.validator.ValidPerson;
import com.test.validator.groups.BasicValidation;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;

@ValidPerson(
    groups = BasicValidation.class
)
public class Person {
  @ValidName
  private String name;

  public Person() {
  }

  public Person(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "Person{" + "name='" + name + "'" +"}";
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person that = (Person) o;
        return java.util.Objects.equals(name, that.name);
  }

  @Override
  public int hashCode() {
    return java.util.Objects.hash(name);
  }
}

대상 사이트의 예:

type Person @deprecated(reason: "This is going bye bye") @annotate(name: "ValidPerson", type: "validator", inputs: {types: [HUSBAND, WIFE]}) {
    name: String @annotate(name: "com.test.anotherValidator.ValidName", target: "field") @annotate(name: "com.test.nullValidator.NullValue")
}

Java에서 POJO를 생성했습니다.

package com.netflix.graphql.dgs.codegen.tests.generated.types;

import com.test.anotherValidator.ValidName;
import com.test.nullValidator.NullValue;
import com.test.validator.ValidPerson;
import java.lang.Deprecated;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;

/**
 * This is going bye bye
 */
@Deprecated
@ValidPerson(
    types = [com.enums.HUSBAND, com.enums.WIFE]
)
public class Person {
  @ValidName
  @NullValue
  private String name;

  public Person() {
  }

  public Person(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "Person{" + "name='" + name + "'" +"}";
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person that = (Person) o;
        return java.util.Objects.equals(name, that.name);
  }

  @Override
  public int hashCode() {
    return java.util.Objects.hash(name);
  }
}

코드 생성 구성

코드 생성에는 많은 구성 스위치가 있습니다. 다음 표에는 Gradle 구성 옵션이 나와 있지만 명령줄과 Maven에서도 동일한 옵션을 사용할 수 있습니다.

Configuration property Description Default Value
schemaPaths 스키마를 포함하는 파일/디렉토리 목록 src/main/resources/schema
packageName 생성된 코드의 기본 패키지 이름 -
subPackageNameClient 생성된 Query API의 하위 패키지 이름 client
subPackageNameDatafetchers 생성된 데이터 가져오기의 하위 패키지 이름 datafetchers
subPackageNameTypes 생성된 데이터 유형의 하위 패키지 이름 types
language java or kotlin Autodetected from project
typeMapping 각 키가 GraphQL 유형이고 값이 Java 클래스의 FQN인 맵 -
generateBoxedTypes 프리미티브에는 항상 박스형 유형을 사용하십시오. false (boxed types are used only for nullable fields)
generateClient 쿼리 API 생성 false
generateDataTypes 데이터 유형을 생성합니다. Query API 생성에만 유용합니다. 입력 유형은 generateClient가 true인 경우에도 여전히 생성됩니다. true
generateInterfaces 데이터 클래스에 대한 인터페이스를 생성합니다. 이는 더 많은 컨텍스트를 위해 생성된 POJO를 확장하고 데이터 불러오기의 데이터 클래스 대신 인터페이스를 사용하려는 경우에 유용합니다. false
generatedSourcesDir Gradle용 빌드 디렉토리 build
outputDir 생성할 generatedSourcesDir의 하위 디렉토리 generated
exampleOutputDir datafetcher 예제 코드를 생성할 디렉터리 generated-examples
includeQueries 지정된 쿼리 필드 목록에 대해서만 쿼리 API 생성 All queries defined in schema
includeMutations 지정된 Mutation 필드 목록에 대해서만 쿼리 API 생성 All mutations defined in schema
includeSubscriptions 주어진 구독 필드 목록에 대해서만 쿼리 API 생성 All subscriptions defined in schema
skipEntityQueries 연합 유형에 대한 엔터티 쿼리 생성 비활성화 false
shortProjectionNames 프로젝션 유형의 클래스 이름을 줄입니다. 이러한 유형은 개발자에게 표시되지 않습니다. false
maxProjectionDepth 생성할 최대 프로젝션 깊이입니다. 매우 깊은 중첩이 있는 (연합된) 스키마에 유용합니다. 10
includeImports 주석이 속한 패키지에 사용자 지정 주석 유형을 매핑합니다. generateCustomAnnotations가 활성화된 경우에만 사용됩니다.  
includeEnumImports 사용자 지정 주석 및 enum 인수 이름을 enum 패키지에 매핑합니다. generateCustomAnnotations가 활성화된 경우에만 사용됩니다.  
includeClassImports 사용자 지정 주석 및 클래스 이름을 클래스 패키지에 매핑합니다. generateCustomAnnotations가 활성화된 경우에만 사용됩니다.  
generateCustomAnnotations 사용자 지정 주석 생성 활성화/비활성화 false

댓글남기기