Search

[Flutter] artemis - graphql code generator

subtitle
flutter - artemis 사용 방법과 장단점
Created
2021/02/25 14:23
tags
오픈소스
graphql
flutter

model, fromJson 노가다

flutter에서 model 클래스 정의하고, fromJson 생성자를 일일이 만들어줘야하는 게 불편했다. 데이터를 가져올 때마다 매번 이래야하니 귀찮아 죽을 것 같았다.
두번째 모델을 만들려다가 flutter에도 왠지 graphql-code-generator 같은 비슷한 라이브러리가 있을 것 같았다.아니나 다를까 3개 정도가 있었는데 그 중 artemis가 document도 잘 정리되어있고 star 수도 많아 쓸만해보였다.

Artemis

아르테미스는 graphql 파일을 찾아 dart 파일을 생성해주는 code generator 기능이 있고 ArtemisClient로 graphql 쿼리도 날릴 수 있다. artemis는 아폴론(apollo)의 쌍둥이 여동생인데 graphql Apollo와 비슷하다고 하여 artemis로 지었다고 한다.
source: github 'comigor/artemis' repo

사용법

스키마 파일 가져오기

flutter, firebase를 같이 써서 모든 스키마 파일이 플러터 프로젝트 안에 있다면 그냥 스키마를 한 파일에 넣으면 된다.
나의 경우에는 nodejs를 server로 사용하고 있어서 graphql-code-generator로 스키마 파일을 한 파일에 모아서 generate했다.
yarn add @graphql-codegen/schema-ast
Shell
복사
codegen.yml
generates: schema.graphql: plugins: - 'schema-ast'
YAML
복사
code generate를 하면 서버에 schema.graphql 파일이 생성된다. 이 파일을 flutter 프로젝트 폴더로 복사한다. (artemis에서는 서버 엔드포인트나 상대 경로로 schema를 가져올 수 없어서 이렇게 했다.)

pubspec.yaml

아래 패키지를 모두 설치해준다. 권장 버전은 바뀔 수 있으니 버전은 artemis 문서를 확인한다.
dependencies: # graphql code generator artemis: '>=6.0.0 <7.0.0' # only if you're using ArtemisClient! json_annotation: ^3.1.0 equatable: ^1.2.5 meta: '>=1.0.0 <2.0.0' # only if you have non nullable fields gql: '>=0.12.3 <1.0.0' intl: ^0.15.8 dev_dependencies: build_runner: ^1.10.4 json_serializable: ^3.5.0
YAML
복사

build.yaml 설정

options 종류는 artemis 도큐먼트에 나와있다.
naming_schema: simpe : generated 되는 클래스 이름들을 간단하게 바꿔준다. default 옵션은 상위 클래스의 이름이 증조클래스$부모클래스$자식클래스 이렇게 prefix로 계속 붙어서 가독성이 떨어진다. 대신 이름이 겹치지 않아서 안정적이다.
schema_mapping : output 파일은 generated된 파일 위치 / schema는 맨 처음 얘기했던 스키마 파일 위치 / queries_glob은 query나 mutation의 쿼리문 파일의 위치를 적는다.
custom_parser_import : artemis가 파싱할 수 없는 타입은 직접 파서를 만들어야 한다. 다행히 흔히 쓰이는 Date같은 타입은 artemis에 예시가 있었다.
scalar_mapping : graphql_type은 graphql 파일 안에 있는 타입, 그리고 dart_type은 graphql 타입을 generate했을 때 dart의 어떤 타입과 매핑할 건지 써주면 된다. custom_parser를 써야하면 use_custom_parser: true 를 사용한다.
targets: $default: builders: artemis: options: #1 options naming_scheme: simple schema_mapping: - output: lib/generated/graphql-api.dart schema: lib/schema.graphql queries_glob: lib/logic/graphql/operations/*.graphql custom_parser_import: "package:example/lib/utils/coercers.dart" scalar_mapping: - graphql_type: GraphQLDate dart_type: DateTime use_custom_parser: true - graphql_type: GraphQLDateTime dart_type: DateTime use_custom_parser: true - graphql_type: JSONObject dart_type: Map<String, dynamic> sources: - lib/** - graphql/**
YAML
복사

generate

아래 명령어를 입력해서 build_runner로 build.yaml에 있는 내용을 실행한다.
flutter pub run build_runner build
Shell
복사
output 파일이 lib/generated/graphql-api.dart 에 생긴다. (3개의 파일이 생길 것이다)

코드 단축

model 클래스나 fromJson 생성자를 만들어주지 않아도 된다. 알아서 만들어준다. 또한, query document도 만들어줘서 string으로 따로 만들 필요 없이 graphql-api.dart 파일만 import 해서 사용하면 된다.
보통은 final search_books = r'''query { ... } ''' 처럼 string으로 작성한 후, gql(search_books)로 query document를 만들지만, artemis를 사용하면 그냥 아래처럼 graphql 파일로 작성한다.
query search_books($q: String!, $page: Int) { searchBooks(q: $q, page: $page) { books { title description isbn thumbnail __typename } } }
GraphQL
복사
그리고 generated 파일만 import 해서 document를 갖다쓰면 된다.
import 'example/generated/graphql-api.graphql.dart'; QueryOptions( document: SearchBooksQuery().document, variables: {'q': q, 'page': page} );
Dart
복사
fromJson도 generated된 클래스의 메서드를 사용하면 된다.
fromJson(QueryResult result) { final books = SearchBooks$Query$SearchBookPayload.fromJson(result.data['searchBooks']) .books; return books; }
Dart
복사
만약에 artemis를 쓰지 않았다면 아래 같은 모델을 만들어줘야 한다.
class SearchBook { final String title; final String description; final String isbn; final String thumbnail; SearchBook({this.title, this.description, this.isbn, this.thumbnail}); factory SearchBook.fromJson(Map<String, dynamic> json) { return SearchBook( title: json['title'], description: json['description'], isbn: json['isbn'], thumbnail: json['thumbnail'], ); } }
Dart
복사
원래 필드가 더 많은데 가독성을 위해 줄였다.
또한, fromJson도 아래처럼 복잡해진다.
List<SearchBook> fromJson(QueryResult result) { final booksData = result.data['searchBooks']['books']; final books = List<SearchBook>.from( booksData.map((book) => SearchBook.fromJson(book))); return books; }
Dart
복사

Artemis의 장단점

artemis는 노가다할 때보다는 확실히 편했다. 하지만 graphql-code-generator와 비교해봤을 때 아직은 불편한 점도 있었다.
장점
model 클래스 정의, fromJson 노가다 안해도 된다.
graphql_flutter, model 파일 등 import 안하고 generated 된 파일 하나만 import 하면 된다.
서버 graphql endpoint에서 graphql schema를 못가져온다는 게 아쉬웠다. 하지만 model 클래스나 Json serializer 를 따로 정의해주지 않아도 되고, graphql document도 gql(document) 로 안쓰고 generated된 타입에서 가져다 써서 graphql_flutter를 따로 Import 해주지 않아도 되서 편했다.
단점
watch가 안되서 graphql 파일에 변경이 생길 때 마다 매번 generate 해줘야 한다.
서버 graphql endpoint에서 graphql schema를 읽지 못한다. ../server/schema.graphql 처럼 상대경로로 읽어 오는 것도 안된다 (glob 패턴으로 프로젝트 폴더 안에서 파일을 찾아 그런 것 같다).
ArtemisClient로도 query, mutation 등의 operation을 실행할 수 있는데, graphql_flutter에 비해 지원되는 기능이 적은 듯하다. code generator 정도로만 사용하면 될 듯하다.
그리고 클래스의 이름이 너무 길다. Payload 타입을 여러 곳에서 써야하는데 클래스명이 BooksData$Query$BookPayload$Book 이렇게 나온다. naming_scheme: simple 옵션이 있긴 한데 이 옵션은 query, mutation의 네이밍을 간단하게 만들어준다. 그러나 query명에 적용되는 것 같지는 않다. 모델을 새로 만들어 사용하는 대신 해당 클래스명을 사용할 수 있는데, 여러 군데서 사용하려니 가독성이 떨어진다.
예를 들어 아래 같은 코드가 있다고 해보자. default 옵션에서는 상위 클래스가 계속 prefix로 붙어 BigQuery$Query$AliasOnThing$AliasOnNextThing 처럼 클래스명이 길어진다. 반면 simple 옵션은 AliasOnNextThing 으로 쓸 수 있다. 그러나 query 명인 BigQuery는 여전히 prefix가 계속 붙는다.
참고: artemis commit code
query big_query($input: Input!) { aliasOnThing: thing(input: $input) { e ...parts nextThing { id } aliasOnNextThing: nextThing { id } } }
GraphQL
복사

references