事業部初導入のGraphQLとTypeScriptを使ったSPA開発に参加

樋口鉄朗氏:合同会社DMM.comオンラインイベント事業部の樋口鉄朗です。(※取材当時)今回は「Nuxt × TypeScript × GraphQLで開発した際のつらみ」についてのTipsを説明していきたいと思います。よろしくお願いします。

私は公立はこだて未来大学出身で、2019年に新卒でDMM.comに入社しました。趣味は自転車に乗ったり、旅行したり、ドライブに行ったりすることで、多趣味なエンジニアです。

以前は動画配信事業部に所属していて、売上グロース施策の関連開発などを担当していました。また、プラットフォーム事業部では、事業部内のシステムのリプレイスなどのプロジェクトを担当したり、運営事業部チーム向けのアプリケーションやbotを開発したりしていました。

現在所属しているオンラインイベント事業部では、オンラインイベント展示会「DMM showbooth」のCMS機能のフロント部分の開発を担当しています。担当している技術領域はJavaScript、React、Next、Nuxt、Vueなどで、さまざまなフレームワークを触っています。またPHPやGolang、Pythonなども少し触っていて、卒業研究ではSolidityで書いたスマートコントラクトを作っていました。ほかにも、「Mastodon」のサーバー管理をやっています。

それではここから本題に移っていきたいと思います。これはとある案件の話です。プロダクトについての詳細はお話できないのでTipsのみの説明です。ご了承ください。

今回のアジェンダです。Vue2とTypeScriptの組み合わせがつらい話と、GraphQLがつらい話の2点です。

「私、2019年新卒のてっちゃん! 既存システムの改修案件が一段落しそうなところで、突然案件が降ってきた! 『ねぇ。ちょっと楽しいことしない?』。NuxtにTypeScriptにGraphQLを使ったフルSPA(Single Page Application)! あまり触る機会のないモダン環境で開発できるよと誘われ、技術領域のモダンさに目を奪われていたら、背後から迫ってくる納期に気づかなかった!リリースは12月、アサインは6月。フロント開発人数は自分を含めて3人。初めてのVue。これから私、どうなっちゃうの〜??」という案件を担当したときの話です。

ざっくりとしたフロントの仕様は、WebViewがほとんどの、ネイティブアプリケーションライクな何かです。すべてキーボードで操作できる必要があります。動作確認対象のブラウザは1つだけですが、デバイスに入っているプリインブラウザです。アクティブフォーカスが見やすいようにアニメーションが必要です。既存のアプリケーションのデザインは、ほぼ移植するので、リプレイスに近いものになります。

技術仕様や置かれた環境ですが、事業部初GraphQLとTS(TypeScript)を導入しました。また、事業部2例目のNuxtを使ったSPAの開発です。GraphQLは、既存のAPIをリプレイスしていくためのBFF(Backends For Frontends)的な役割として導入することが決定しました。TypeScriptはJS(JavaScript)の“つらみ”を軽減できるのでは? と試験的に導入することになりました。

SPAの開発は以前の経験があったので、そこまでは苦労はしないだろうという環境での開発でした。

Vue2.6ではTypeScriptサポートの恩恵が受けられなかった

では本題に移りましょう。「Vue2×TypeScriptがつらい! 〜Vue3以前の型推論のない世界〜」にご案内します。嫌ですね、そんな世界。この案件を開発したときは、2.6がVueの最新だったので、Vue3のTypeScriptサポートが受けられなかったということを前置きしておきます。

まず、Vueについて解説をします。言わずと知れた、UI構築のためのJS(JavaScript)のフレームワークです。プログレッシブフレームワークという、部分採用可能な仕組みを取り入れているので、HTMLの一部だけをVueに置き換える書き方ができて、弊社のシステムの一部でもこれが動いています。

また、1つのファイルにHTMLのテンプレート構文を記述して、HTMLタグ、styleタグ、scriptタグで、各ロジックを分離して書けるので、1つのファイルで1コンポーネントを実現する単一ファイルコンポーネントと呼ばれる書き方が可能です。

具体的にはこんな感じですね。templateタグ、scriptタグ、styleタグと分けて書くところで、本来だったらファイルが全部別れているものを全部1個にまとめて書けるのがVueの特徴です。

続いて、TypeScriptの話をしておきます。動的型付けのスクリプト言語であるJavaScriptに対して、静的型付けを提供するスーパーセットです。とても良いものなんです。ただ、「VueのTypeScriptのサポートが完璧だったならば」という前置きが入るんですね。

ここがつらいよ、Vue。Vue2.6でTypeScriptのサポートがかなり進んだので、TypeScriptを入れようと気運が全体的に高まっていた時期でした。しかし、Vue2系のTypeScriptサポートは型推論がほぼ使えない状態でした。例えば、computedにhasNext()みたいなメソッドを生やした場合、このhasNext()の返り値は内部の型推論が効かないのでany型になります。

TypeScript tscがstrict modeの場合は、エラーになることがほとんどです。さらに言うと、booleanと型明示を絶対付けないといけないのはもちろん、1ヶ所でもこれが欠けていると、かなりの高確率でVS CodeのVueのlinterであるveturがプロジェクト内すべてを指して、シンタックスエラーですと吐いて、エディタが真っ赤に染まります。

また、「Vue Apollo」を使っていたんですが、ここの書き方が正しくてもVue ApolloのTypeScriptサポートが中途半端だったので、アサーションをしないとまともに型周りのサポートが入らないという残念な仕様でとても苦労しました。

どう書いてもany型になるので、どこかしらでアサーションをかける必要があります。 TypeScriptを書いているのにアサーションを使うということに負けを感じて、「なんかなぁ」という感じでした。

また、Vueに「dollar」プラグインみたいなものを入れていると、これも型が付かないんですね。Vueのdeclareで型を明示してもなぜか吐かなくて、この下のほうに書いてあるconst dollarApolloみたいな感じで、「そのthis.$apolloはDollarApolloですよ」という型を入れないと、まともに型が付かないのがVue2.6のTypeScriptサポートのつらい世界でした。

今はVue3が正式に登場していて、Composition APIの登場で、ReactのFunctional Componentみたいなものが作れるようになりました。しかもVue2よりも厳格で、きちんと型推論の効くdefineComponentという型が登場したおかげで、TypeScriptをVueで書くのがかなり楽になりました。

実際にお試しで、運営さん向けツールとして作ったものがあるんですが、きちんと型推論が効いていました。今までだったらany型になっていた、downloadLinkEnable()もきちんとbooleanと型推論が出ているので、「あぁ、すごい!」と、感動を覚えましたね。

みなさんは苦労しなくていいと思います。とりあえずVue3を使いましょう。この問題に直面したときのVue2.6はとてもつらかったです。何も言いません、Vue3を使ってください。

調整不足により多種多様なスキーマ名が誕生

では次の「GraphQLがつらい! 〜レガシーAPIベースで作り上げたクエリに支配された世界〜」の話に移ります。これはまた嫌な世界ですけどね。

API向けのクエリ言語のGraphQLですが、REST(Representational State Transfer)で使われるopenapi.jsonやhoge.json(任意のjson)みたいなファイルに代わってgraphql.schemaというスキーマファイルを定義することでクエリを構築していきます。

REST(Representational State Transfer)のAPIと異なって、1リソースごとではなく、そのリソースの必要な情報のみをクエリ定義できるのが特徴で、必要な情報のみをやることによってリクエストを最小限にしたり、DB負荷を抑えるたりできるのが特徴です。スキーマ設計が正しければ……です。

もともとはREST(Representational State Transfer) APIを使う予定だったんですが、バックエンドの技術力向上と将来性を見込んでGraphQLを使いたいとなりました。「あ、ふーん」と、この時点でちょっと私は暗雲が垂れ込めているなと感じていました。

GraphQLは、レガシーAPIからの置き換えを将来達成するためのBFF(Backends For Frontends)みたいな中継ツールとして使っていました。なので表側のリクエストはGraphQLでできますが、裏は今までどおりレガシーAPIでリレーしていました。このレガシーAPIは、機能ごとの関数がAPIとして生えているSOAP(Simple Object Access Protocol)的なものだったので、結果としてAPIの数だけクエリが生えるという状況が生まれてしまいました。

また、スキーマの命名規則について、フロントエンドとバックエンドのエンジニア間の合意がうまく取れていなかったのに加えて、バックエンドエンジニアとの調整不足により、「hogeItemListItem」「hogeListItem」「HogeItem」みたいな、多様性溢れる命名がたくさん誕生してしまって、フロントエンド側が「なんじゃこりゃー!」という状況になりました。

また、TypeScriptを利用したときに、「GraphQL Code Generator」でスキーマファイルから型定義を自動生成して、レスポンスに対して型を自動で当てていた結果、全部違う命名がそのままフロントエンドに返ってきて「命名違いすぎ! なんでや!」という状態になってしまいました。

QA期間を経て、1ヶ月間でリファクタリングを実施しました。スキーマファイルを一括で直す作業が行われたのですが、ローカルの開発サーバーを立ち上げるときにスキーマファイルを再取得するフロントエンドの処理をpackage.jsonのスクリプトに書いていたので、すべてのフロントエンドのPCで開発環境が壊れるという事態が起きました。「開発環境オワタ」という感じですね。

「GraphQL」を使うときは必ずスキーマ駆動開発を使う

いろいろな“つらみ”が多いので、GraphQLを利用する場合は、必ずスキーマ駆動開発を使っておきましょうという教訓です。openapi.jsonやgraphql.schemaなどのスキーマ定義を、事前にフロントエンドとバックエンドの間で、要するに開発者と利用者の間で合意を取ってからAPIを作りましょうというのがこのSchema Driven Developmentです。

すでに完成後のインターフェイスがわかっているので、だいたいのライブラリを使えばモックサーバーもすぐに立てられます。フロントエンド側の開発がバックエンドのサーバーのデータの出来上がりを待たなくても、データがすでに完成した状態で開発を進められます。

また、開発における手戻りの発生リスクもとても小さいので、教訓は「スキーマ駆動開発はぜひ使っていきましょう」になります。そもそもGraphQLは本当に必要なのかも検討する必要があるかなと思います。GraphQLは仕組み上、かなりしっかりとした設計がないとあまりメリットがないかなと思っています。

特に今回のように工期やDB、APIの都合上、インターフェイスの自由度はほぼない状態で、機能がリソースに紐づいているのではなくて、リソースが機能に紐づいている、要するにSOAP(Simple Object Access Protocol)的なAPIの張り方をしている場合、GraphQLはBFF(Backends For Frontends)みたいな使い方がぜんぜん適していないんじゃないかと思います。

あとは新しい技術を使ってみたいだけで、GraphQLを使おうとしているのであれば絶対に良くないと思います。きちんと適材適所で使いましょう。

教訓です。モダンな技術もレガシーな技術も適材適所で使っていきましょう。プロダクションに入れるのは熟れてからです。自分用に使うのであればガンガン使っていきましょう。私の発表は終わりです。