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
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가 계속 붙는다.
•
query big_query($input: Input!) {
aliasOnThing: thing(input: $input) {
e
...parts
nextThing {
id
}
aliasOnNextThing: nextThing {
id
}
}
}
GraphQL
복사