[Flutter] graphQL codegen에 대해서

saewoohan·2024년 5월 9일
0

Flutter

목록 보기
12/12
post-thumbnail

1. Graphql codegen은 무엇인가?

  • 모든 data관련 request나 response는 각각에 맞는 key, value를 넣어 주어야한다.
  • RestAPI나, graphQL이나 타입을 규정하고 있는 언어에 대해서는 주고받는 데이터를 타입화 시킬 필요가 있다. 특히, 강하게 type check을 사용할 수록 데이터의 타입화의 필요성은 더욱 커진다.
  • 간단히 Table한개의 CRUD만 구현하더라도 4개의 API, 어느정도 규모가 커지는 프로젝트일 수록 비지니스 API도 많아지며, Table의 개수도 많아지기에 API의 개수가 엄청나게 많아진다. 그 뜻은, 그 만큼의 API에 대해서 타입을 일일히 지정해줘야하는 것이다.
  • 이 모든 API들에 대해서 문서를 보고, 타입을 선언하고 유지하는 과정에 소요되는 시간도 적지 않고 매우매우 귀찮은 작업이다. 또한, 기존에 작성되어있던 API에 대해서 수정되는 부분이 있다면 일일히 찾아서 수정해줘야하는 번거로운 과정도 존재한다.
  • 그래서 이를 해결해주는 라이브러리 graphql-codegen이 존재하는 것이다. GraphQL Schema를 타입, (정확하게는 flutter에서는 class)으로 자동으로 변환해주는 라이브러리이다.

2. GraphQL Codegen

GraphQL Codegen 공식문서

  • graphQL codegen 중 flutter에 대해서는 flutter-freezed와 결합해서 더욱 편리한 Schema class를 제공한다.
    - flutter-freezed는 class에 대해서 copy, toString, toJson 등등 다양한 유틸함수 또는 변수를 자동으로 만들어주는 편리한 라이브러리이다.
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
 
part 'app_models.freezed.dart';
part 'app_models.g.dart';
 

class RequestOtpInput with _$RequestOtpInput {
  const RequestOtpInput._();
 
  const factory RequestOtpInput({
    String? email,
    String? phoneNumber,
  }) = _RequestOtpInput;
 
  factory RequestOtpInput.fromJson(Map<String, dynamic> json) => _$RequestOtpInputFromJson(json);
}
 

class VerifyOtpInput with _$VerifyOtpInput {
  const VerifyOtpInput._();
 
  const factory VerifyOtpInput({
    String? email,
    String? phoneNumber,
    required String otpCode,
  }) = _VerifyOtpInput;
 
  factory VerifyOtpInput.fromJson(Map<String, dynamic> json) => _$VerifyOtpInputFromJson(json);
}
 

class AuthWithOtpInput with _$AuthWithOtpInput {
  const AuthWithOtpInput._();
 
  const factory AuthWithOtpInput.requestOtpInput({
    String? email,
    String? phoneNumber,
  }) = RequestOtpInput;
 
  const factory AuthWithOtpInput.verifyOtpInput({
    String? email,
    String? phoneNumber,
    required String otpCode,
  }) = VerifyOtpInput;
 
  factory AuthWithOtpInput.fromJson(Map<String, dynamic> json) => _$AuthWithOtpInputFromJson(json);
}

1) 설정법

  • graphql codegen은 우선 pub.dev에 직접적으로 올라와 있는 라이브러리는 아니여서 node package manager를 따로 사용하여야한다.
yarn add graphql
yarn add --dev typescript -codegen/cli -codegen/flutter-freezed
  • 이렇게 라이브러리를 추가해주면, codegen을 사용할 준비는 끝났다. 추가적으로 flutter프로젝트에 package.json을 추가한 것이기에 .gitignorenode_modules를 꼭 추가해주도록 하자
  • 그 후 codegen.yml파일을 하나 추가한 후 codegen에 대한 정보를 적어주도록 하자.
overwrite: true
schema: ${SERVER_URL}
generates:
  ./lib/feature/data/schema.graphql:
    plugins:
      - schema-ast
  ./lib/feature/data/models.dart:
    plugins:
      - flutter-freezed:
          camelCasedEnums: true
          copyWith: true
          useEquatable: true
          customScalars:
            DateTime: DateTime
            JSON: Map<String, dynamic>
  • 나는 이렇게 사용하고 있는데, 각각의 key에 대해서 간단히 설명하자면, SERVER_URL에서 schema를 가져와서 schema.graphql에는 codegen에 사용되는 graphql파일을 생성하고 models.dart에서는 flutter project에 사용될 수 있도록 .dart확장자로 변환을 해준다.
  • flutter-freezed의 key중 하나인 customScalars는 graphql type중 Scalar type에 대해서 flutter에서 사용할 수 있도록 type을 매핑해주는 역할이라고 보면 된다.
  • 나는 cross-envdotenv-cli를 통해 SERVER_URL을 환경변수로 관리하여 codegen을 해주었다.
  • 이렇게 codegen을 통해 만들어진 models.dart에는 graphql 서버의 모든 type들이 만들어져서 class로 생성이 된다. (args type도 포함)
  • 직접 각각의 response type과 args type에 대해서 선언해서 만들어줄 필요 없이 간단하게 스크립트 한번만으로 서버와 Schema동기화가 가능하다.
  • 심지어 방금 따끈하게 추가 혹은 수정된 Schema에 대해서도 스크립트 한번 만으로 최신화가 가능하다는 점이 정말 편리하지 않을 수 없다.

3. Flutter GraphQL Codegen

graphql_codegen

  • 아니 방금까지 Flutter의 GraphQL Codegen에서 설명하고 이제 끝이 아닌가? 이 테마는 무엇일까?
  • 저번에 소개했던 flutter의 graphql, flutter_graphql라이브러리에서 직접적으로 소개하는 pub.dev의 codegen라이브러리가 있다.
  • 통상적으로 GraphQL Codegen라고 불리는 the-guild.dev의 codegen과는 다른 방식과 다른 Publisher이지만, codegen의 의미는 사실상 같기에 같이 소개를 해보려고 한다.
  • 해당 라이브러리의 장점은 간단하다. mutation과 query에 대해서도 codegen을 해준다는 사실이다.
  • 기존 graphql을 사용하기위해서는 String type의 documentation을 선언하여 할당해 주었어야 했다.
String addStar = """
  mutation AddStar(\$starrableId: ID!) {
    addStar(input: {starrableId: \$starrableId}) {
      starrable {
        viewerHasStarred
      }
    }
  }
""";

...dart

Mutation(
  options: MutationOptions(
    document: gql(addStar), // this is the mutation string you just created
    // you can update the cache based on results
    update: (GraphQLDataProxy cache, QueryResult result) {
      return cache;
    },
    // or do something with the result.data on completion
    onCompleted: (dynamic resultData) {
      print(resultData);
    },
  ),
  builder: (
    RunMutation runMutation,
    QueryResult result,
  ) {
    return FloatingActionButton(
      onPressed: () => runMutation({
        'starrableId': <A_STARTABLE_REPOSITORY_ID>,
      }),
      tooltip: 'Star',
      child: Icon(Icons.star),
    );
  },
);

...
  • 하지만 .graphql확장자로 graphql query와 mutation을 직접적으로 할당해준다면, codegen을 통해 dart파일을 얻을 수 있는 것은 물론, 관련한 타입들까지도 codegen을 통해 만들 수 있다.
# person.graphql

query FetchPerson($id: ID!) {
  fetch_person(id: $id) {
    name: full_name
  }
}
import 'person.graphql.dart';


main () async {
  final client = GraphQLClient();
  final result = await client.query$FetchPerson(
    Options$Query$FetchPerson(
      variables: Variables$Query$FetchPerson(id: "1"),
    ),
  );
  final parsedData = result.parsedData;
  print(parsedData?.fetchPerson?.name);
}
  • 이런식으로 .graphql파일을 만들어준 후, codegen (flutter_freezed 과정도 포함)을 거치고 나게 되면, 훨씬 간단한 문법 작성으로 query와 mutation을 작성할 수 있다, 추가적으로 기존에 graphql을 사용할 때는 단순히 Key, value의 object로 variables를 할당해 주었다면, typing이 가능해 런타임에서 에러를 파악할 수 있다는 장점이 있다.

1) 설정법

$ flutter pub add --dev graphql_codegen build_runner
  • 간단히 build.yaml파일을 생성한 후 build_runner를 수행해주면 세팅은 끝이다.
targets:
  $default:
    builders:
      graphql_codegen:
        options:
          clients:
            - graphql
          scalars:
            JSON:
              type: Map<String, dynamic>
            DateTime:
              type: DateTime
  • 마찬가지로 codegen에 대한 각각의 문법이 있는데, 공식문서에 잘 나와 있는 편이니 필요한 부분은 참고하면 된다.

3. 총평

  • codegen은 개발적으로 시간을 많이 줄여주고, 귀찮은 작업을 돌아주는 아주 고마운 친구다..!
  • 사실 이렇게 codegen을 사용하면, entitymodel을 대체하는 개념이다 보니, 오히려 프로젝트 관리가 더 용이한 느낌이 들었다. 나는 GraphQL파일들에 대해서 각 테이블별로 디렉토리를 분류해놨는데, 개인적으로 조금 더 백엔드에 가깝게 data 로직 관리를 할 수 있다고 느꼈다.
  • 단점이라고 한다면, 당연히 높은 라이브러리 의존도로 인한 안정성 문제일 것이다.. 앞서 소개한 graphql_codegen pub.dev라이브러리도 아직 0번대 버전으로 안정성은 크게 없는 것 같다. 빠른 개발 속도가 중요하고, 귀찮은 작업을 할 시간이 부족하다면 사용하고, 아니면 사용을 지양하는 편이 좋을 것 같다.

0개의 댓글