コロナ禍での仕事の変化

Changhee Kim氏:それでは「コロナ禍、私たちにできたこと」というタイトルで発表させていただきます。まず自己紹介ですが、Kim Changhee(キム・チャンフィ)と申します。LINEでは2018年からフロントエンドエンジニアとして働いています。

LINEではたくさんの外国籍社員が働いています。私もその1人で、韓国出身のエンジニアです。ふだんはLINE公式アカウントの管理画面アプリを開発しています。とにかくVue.jsが好きで、仕事でもVue.jsをたくさん使っています。

さて、みなさんどう過ごしていますか? 私にとっては仕事のやり方に大きな変化はあまりないのですが、3月からずっと在宅勤務をしています。このようにとても落ち着いた環境で仕事をしています。

そして会議は、もともとやっていたんですが、ZOOMを使ったリモート会議をしています。また、私は飲み会が大好きなのですが、お店に集まることはできないので、リモート飲み会をやったりもします。これはチームの飲み会の様子なのですが、LINEのビデオ通話機能がいろいろ遊べて便利なのでおすすめします。

個人的にはVueConf USに行く予定だったんですが、直前に渡航ができなくなってとても残念でした。

そして、いろいろ便利な時代なので、ネットでいろいろ頼んだりして間違いなく太っています。でも、家族との時間が増えたのはいいことだったかなと思います。

新型コロナ対策のための全国調査

さて、今年の3月末を振り返ってみましょう。当時国内のコロナの感染者数は増加傾向になっていました。そこでさまざまな対策が打ち出されたわけですね。

LINEで行った施策の1つとして、「新型コロナ対策のための全国調査」というものがありました。すでに目にしている方もたくさんいらっしゃると思います。

簡単に概要を説明しますと、厚生労働省の新型コロナ対策に協力するために実施したアンケートとなります。計4回実施していて、対象はLINEの国内アクティブユーザー約8,300万人(※実施時点)でした。各回の有効回答者数は2,000万人以上となり、非常にたくさんの回答をいただきました。調査結果は、感染状況の把握や感染拡大防止のための有効な対策検討に活用していただくために、厚生労働省に提供いたしました。ユーザーのみなさん、ご協力ありがとうございました。

開発当時のスケジュールを見てみると、アンケートを実施するという話をもらってからリリースまで、たった1週間しかないのがわかります。仕様の検討やQAテストの時間を除くと、開発に使える時間は実質3日間というタイトなスケジュールでした。どうしても3月31日にリリースしたかったのは、翌日4月1日のエイプリルフールを避けたいという理由もありました。

このプロジェクトを進める上ではいろいろな調整が入るわけですが、総勢50人を超えるさまざまな職種の社員が一丸となって参画しました。

その中で、アンケート画面の開発だけで言うと、フロントエンドが私1人で、サーバーサイドは2人のエンジニアがメインとして参加しました。そして配信やプロモーション、プラットフォームを担当するエンジニアにもたくさんご協力いただきました。すべての打ち合わせはもちろんリモートで行っています。

自前のシンプルなシステムを開発

プロジェクトのゴールは非常に明確でした。アンケートを約8,300万人に配信して、2日間の回答結果を集計して、そのデータを厚生労働省に提供するというものになります。

そこで、どういった手段でアンケートを実施するかを検討しました。サードパーティーを使うのは、このような理由で最初から考慮しませんでした。

LINEにはLINEアンケートというプラットフォームがあってアンケート自体は実施できますし、それでもよかったんですが、アンケートは複数回実施するということで、今後出てくる要件、要望をすべて満たせるかどうかが不明だったことと、これから負荷対策を考えないといけないのが問題でした。

そこで残る選択肢として、自前でシンプルなものを作ってしまおうという決定をしました。シンプルなものとは? アンケートを取るためには、これさえできればいいんですね。ユーザーがアンケートに答えたらそれをDBに貯める。これができればいいんです。

しかし、どうしても気になることがあります。それは負荷という懸念でした。さすがに8,300万人が同時にアクセスすることはないと思うんですが、それでもアクセスが集中することは容易に想像できます。

定番の方法をとるならスケールするようなアプリを開発すると思いますが、今回は開発とテストに十分な時間が与えられていないんですね。しかし、終始高い負荷がくることはたしかなので、負荷による遅延が起きにくいシステムにしようじゃないかという結論に至りました。

負荷を最小限にできるシステムに

負荷を最小限にできるシステムをどう作ったかを見てみましょう。まずアンケートページはサーバーではなにも判定せずindex.htmlを返すだけにしました。これがおそらくリクエストからレスポンスまでの最短のルートだと思います。

必要なページの分岐処理はフロントエンドのみで行って、サーバーは最低限の役割だけをします。ある意味エッジコンピューティングを実現しているのかもしれません。もちろん、Webサーバーは多めに用意しておく必要があります。

アンケートに回答するためのAPIはというと、ここでもWebサーバーは静的に200を返しています。とくにDB保存したりする動作は一切ありません。

その代わり、Webサーバーにはユーザーの回答内容を含んだアクセスログが貯まりますね。貯まったアクセスログは定期的にfluentdを介してmysqlに流しておきます。このログ回収の過程が非同期で行われるので、Webサーバー側でボトルネックになることはありません。

あとで改めて話しますが、mysqlに貯まったログは有効性チェックをする必要があります。そのフローも完全に非同期で行われるので、たとえ遅延が起きてもWebサーバーに影響を与えることはありません。

ここまで聞いて、「あれ?」と思う方がいらっしゃるかもしれません。そうですね。UIT Meetupはフロントエンドのイベントでしたよね。実は、ここまでがバックエンドを含む背景の説明になります。

ここからが私が実際に担当した部分で、今日の本題でもあるアンケートのフロントエンド開発について話します。話は3つありますが、koromo、Vue.js、LIFFで分けて話します。

フレームワークの選定理由

まずkoromoです。去年のLINE DEVELOPER DAYでも紹介があったと思うんですが、koromoはCSSフレームワークとして利用しました。簡単に言うと、bootstrapのLINE版になります。デザインがなくてもLINEらしいUIが簡単に作れるので、今回のような急ぎの案件には最適なオプションでした。

私が担当しているLINE公式アカウントの管理アプリでも採用しています。なにより使い慣れているということがメリットで、迷いなく実装が進められるという点から今回採用いたしました。

次にVue.jsです。これを選んだ理由も私自身が使い慣れていることに尽きます。ほかの軽量のフレームワークも検討したんですが、Vue.jsなら何ができて何ができないかすでにわかっていますし、迷いなく前に進むためにはこの一択でしたね。その代わり、軽量化はtree shakingを最適化するとか、dynamic importなどでがんばってみました。

次に画面構成です。エラー画面などもありますが、正常系だと基本的にはこの2画面のみになります。1つのviewでしか状態を保持する必要がないので、storeもrouterも今回は用いません。

コンポーネントの作成

では開発を始めるのですが、まずはコンポーネントの作成から始めます。いただいた仕様では、質問はこの4タイプがありました。

コンポーネントを作るというと、理想では再利用を効かせるために細かく分割したいところです。よく聞くAtomic Designですね。こういうふうに質問のコンポーネントを集めて1つのアンケートのテンプレートが完成するはずです。

でも、いざコンポーネントを分けようと思ったら、そのほとんどが実際bootstrapのクラスが付いただけの単なるエレメントであることに気づきました。なにより、今回はシンプルなアプリケーションなので、再利用のメリットが大きくないんですね。例えば、チェックボックスの場合は複数の選択式のコンポーネントでしか使いません。

ここで高いところからもう1回問題を見渡してみました。そうすると、結局アンケートって質問の集まりですよね。

今回は簡単にするために、各質問をコンポーネントの最低限の単位として見ました。そこからより細かくコンポーネントを分割するのは開発者目線ではたしかに意味があるかもしれませんが、今回は肝心な再利用のメリットが少ないということで、こういうシンプルな構成にしています。

シンプルな質問コンポーネントができました。挙動も単純なので複雑な設計も必要ありません。Propsで質問内容を描画して、選んだ回答はv-modelで親に伝えるだけですね。

最終的に、質問のタイプによってこれら3つのコンポーネントを用意しました。選択式の場合はmultipleというオプションで2つを分けるようにしました。

こうやってできた質問コンポーネントですが、Propsで描画に必要な情報を受け取ります。例えば、こんなふうにtitleとlabelというPropsを決めますね。

見てみたらこういうパターンもあったので、今度はsubtitleというPropsも足しましょう。

しかし、これからの仕様というのはどんなものがくるかわかりません。なので、いつまでもPropsを追加し続けるということになるかもしれません。

その段階で私はslotを使うことを検討しました。別のコンポーネントを入れることも簡単にできますし、柔軟に修正ができるのでコンポーネントをシンプルにすることにも役立つと思います。

アンケートページの組み立て

では、コンポーネントが完成したので、あとはこれを組み合わせてアンケートページを組み立てるだけですね。通常であれば、まずアンケートに必要なデータのかたちをこのように定義して、それを描画することが多いと思います。なぜならアンケートのデータはAPIからもらって描画することが多いからです。

しかし、コンポーネントと同様に、今後どういったスペックの変更や要望がくるかわかりません。必然的にデータの定義も拡張し続けないといけないかもしれません。ときにはプロパティに関数を入れないといけないといった必要性が出てくるかもしれません。

幸い、今回はアンケートに関するデータはすべてJSのバンドルに含まれます。別途APIからアンケートの内容を取ってくるといったことはありません。

そこで、私はアンケートに必要な情報をこのように分けてコードにすることにしました。データセットとマークアップ、そしてロジックです。データセットは実際に集計された値がそれぞれ何を意味するかという非常に重要な情報を持っているので分離しておくのが望ましいです。

ユーザーが見る実際のアンケート画面ですが、マークアップとできるだけ一致させることにしました。書いたものがそのまま画面になるので、順番を変えたりなにか内容を足したくなったときにも直観的にどこをどう修正すればよいかわかるようになります。開発者にとっては、ある意味WYSIWYGに近いかたちになります。

そしてロジックの部分です。ここではアンケートのタイプによって見せる質問のリストとバリデーションを定義します。

バリデーションに関するVue.js的に便利なプラグインはいくつかあると思うんですが、今回は直観的に何をどのようにどういう順番でバリデーションするかを記述できる仕組みを自前で実装しました。そんなに長いコードではなかったです。少し冗長にはなるんですが、手入れは非常にしやすくなりますね。

そして、最終的に送信されるアンケートの回答はgetBodyという関数の中で成形されます。回答データはここ以外ではいじられることがないようにしているので、壊れにくい構造になっています。

このように各パーツの役割を分けてコードを書いて、それをレンダリングした結果に近づけることで直観的な作りにしています。もちろん高度に抽象化されたアンケートのオブジェクトをがんばって定義して、その形式に沿ったデータさえ入れればあとはよしなにアンケート画面を生成してくれるというシステムもクールで悪くはないんですが。

今回は多少冗長にはなりますが、直観的な作りで確実に動いて、かつ柔軟に手を入れやすいというかたちを目指しました。中の動きが完全に見えるのでブラックボックスにならず、頑丈なコードになります。

「LIFF」を利用した理由

次に、今回利用したのがLIFFです。LINE Front-end Frameworkの略で、Webアプリケーションを作るためのプラットフォームになります。LINEアプリ内で動くアプリをシームレスで提供できます。SDKは公開されているので、誰でもLIFFアプリケーションを作ることができます。

LIFFを使った最も大きな理由は、配信も回答も結局LINEアプリ内で提供するという前提だったからです。そして今回は、LINE Loginを使った認証というのも1つの理由になっています。

バックエンドの紹介でも出てきた図なんですが、今回のアンケートはアビュージング対策を考えなくてはいけません。データの信頼性を担保するためには各回答が本当に個人のユーザーから来たものかどうかを検証する必要がありました。

実際、サーバーサイドでAPIでPOSTした時点ではそのような判定は行いません。負荷を軽減するためです。

ここでLINE Loginを使った有効性検証を行いました。LIFF SDKを使うと、ユーザーIDとIDトークンという個人が特定されないかたちの認証情報が取得できます。その情報をアンケートの回答と一緒に送ることで、それが不正でないか検証し、有効な回答のみを集計しました。

実はLIFFではアクセストークンという検証手段もあるんですが、アクセストークンはLIFFアプリを閉じると無効になります。なので、検証が非同期で行われる今回のような場合では使えませんでした。あくまでもユーザーIDが有効かどうかをチェックするためだけだったので、IDトークンを使って今回は検証を行いました。

そして、LIFFを使うもう1つの理由は、LIFFアプリはブラウザでも提供できるという点です。今回は回答期間が約2日間と非常に限られていたので、なるべくみなさんに拡散していただいてたくさんの回答を集めたかったんです。そこで、回答完了画面にTwitter、Facebook、LINEでのシェア導線を用意して、その先はPCからでもスマホからでも利用できるような構想をしました。

LIFFアプリはLIFF URLというユニバーサルリンクに対応したURLで適用されます。スマホなどでLINEアプリが入っている環境では、LINEアプリが立ち上がってLIFFが利用できます。そしてPCなどのブラウザ環境では、LINE Web Loginを介して認証を含めた同様の機能を提供することができます。なので、今回のアンケートの要件にもちゃんと使えるものになります。

LIFF SDKではブラウザで利用するためのAPIをたくさん用意しています。うまく使うとLINEアプリとPCそれぞれに最適化された体験を提供することも可能でしょう。ご興味ある方はドキュメントを見てみてください。

そして、これはおまけになりますが、LINEのシェアボタンも簡単に利用できます。LIFFアプリと同様にPCでもLINEアプリでも利用できるので、非常に便利だと思います。これももし機会があれば試してみてください。

配信当日の回答数の推移と結果

こうして無事にリリースすることができました。配信の当日は、このようなメッセージが全国に配信されたかと思います。

このメッセージはFlex messageという形式なんですが、JSONで作られています。使い方はドキュメントに出ているので、興味ある方はAPIで送るなり試してみてください。

それぞれのボタンにアンケートのLIFF URLを設定していて、選んだ項目によってアンケート内容の出し分けをするのですが、それもJS側でクエリパラメータを見て出し分けを行っています。

これが当日の回答数の推移です。メッセージ自体は負荷の分散のために少しずつ配信をしています。

縦軸を見ていただけるとわかると思いますが、半日も経たないうちに回答数はすでに2,000万件を超えていることがわかります。その間は常に高い負荷がかかる状態だったにもかかわらず、とくに止まることもなく耐えることができました。これは第3回実施時のイメージなんですが、4回ともとくに問題なく耐えることができました。

アクセシビリティに対するアプローチ

開発の話はここまでです。こうして無事にリリースしたんですが、すべてがよかったわけではありません。例えば、アクセシビリティという課題にもぶつかりました。

みなさんからたくさんのご意見をいただく中でも、アクセシビリティに対する指摘はたしかに私たちフロントエンドエンジニアに刺さりました。

例えば、このメッセージがVoice overで読み上げられないといった意見がありました。現在は対応済みです。

アンケートの画面で言うと、基本的にセマンティックなマークアップで作っているので、ひと通り回答するのに問題はなかったです。だからと言って特別に力を入れたわけではありませんでした。

好みの問題かもしれませんが、よくできる部分はいくつかあったと思います。例えばここ、「キューイチ」と読んでほしくないとか。

あとは視覚でしか伝えることができないこのようなUIですね。この場合、この選択肢を選ぶとほかの項目は選べなくなるので、これをどう伝えるかという課題がありました。

そこでスクリーンリーダーを意識して改善を行いました。その1つがスクリーンリーダー向けのテキストの追加です。動画をご覧ください。

(動画が流れる)

このように一般のユーザーには見えないけれどスクリーンリーダーには読まれるようなテキストをたくさん仕込んであります。そして、バリデーションにひっかかった場合の挙動もなるべく自然に。目を閉じても本当に回答できるかを試しながら工夫しました。それでもまだまだできることはあったと思います。

あえてチャレンジしなかったことが正解だった

それでは、今日のまとめです。今日は幅広くいろいろな話をさせていただきました。決して手の込んだ技術を使ったわけではありません。どちらかと言うとシンプルに近かったと思います。

今回の開発は、ズバリ後戻りができない、前に進むしかないものでした。そこで私たちは使い慣れた技術を駆使して、なおかつ直観的な設計を目指すことで確実に動くことを担保することができました。

このやり方は流行りや定番と言われるような方法とは異なるかもしれません。もちろん開発者にとってはふだんから使ってみたかったイケてる技術を取り入れてみたり、今っぽさを求めたいところなんですね。しかし、今回はあえてこのようなチャレンジを入れないことが正解だったように思います。もちろん開発期間にもっと余裕があれば、チャレンジするに越したことはないと思います。

そして、LIFFの使い勝手がとてもよかったので、みなさんにもおすすめしたいと思います。ぜひお試しください。けっこういろいろなところに活用できると思うので、活用の幅は無限にあると思いますね。

最後にアクセシビリティの追求ですね。今回はやはり完璧ではなかったので、これからも私が仕事で作るいろいろなプロダクトでは力を入れていきたいと考えます。

まだ家での生活が続くと思いますが、がんばって乗り越えましょう。ありがとうございました。