とろろこんぶろぐ

かけだしR&Dフロントエンジニアの小言

2021 HackDay フロントエンド開発振り返り

2021 HackDay

Yahoo 主催のハッカソン HackDay 2021 に参加しました。

https://hackday.jp/

開催日: 2021年03月20日 12:00 - 2021年03月21日 12:00 場所: オンライン 結果: 27日なのでまだ。

作ったモノ

「ぎょうれつや」

オンラインで行列に並んだり、自分で行列を作ることができるサービス。

  • Twitter でログインすると行列にならんだり作れたりする
  • 並ぶ時にコメントできて、みんなのコメントを眺めることができる
  • レコメンド機能でツイートや並んだ行列からおすすめの行列を提案してくれる

gyoretsuya.mocchiri.club

メンバーと担当

  • リーダー/インフラエンジニア
  • 機械学習エンジニア/バックエンドエンジニア
  • APP エンジニア/プレゼン/全体PM
  • デザイナー/フロントエンドエンジニア
  • フロントエンドエンジニア (←これ)

もともと4人が知り合いで、 フロントのエンジニアとして声をかけてもらい参加することになりました。

利用技術

FE

  • TypeScript: 型はスピードの足枷にはならず安定性の担保になった
  • Next.js (React): zero config は最強
  • styled-components: 使い慣れてるだけ
  • Firestore: 開発スピードの優先

細かくは下で書く。

BE/Infra

開発準備

フレームワーク選定

UI はゼロコンフィグで React を書ける Next.js にしました。

Next.js ならフレームワークとしてさまざまな形式に即座に対応可能だし、 もうひとりのフロントエンドエンジニアも React が書けるので迷わず決定しました。

さまざまな形式の例

- CSR のみ
- SSR + CSR 
- BFF で API ブリッジあり

結果的に CSR のみになりました。

デプロイ環境

k8s が用意されていたものの一から dockerfile 作るまでやれないと判断し、サービスに頼ることにしました。

Next.js なら Vercel でデプロイできるので、第一候補は Vercel でした。

push で環境をデプロイ追加可能だし、PR 環境もあるし最高と思っていたが、Vercel は GitHub Organizations だと Pro プラン($20/mo per member) に入る必要があることを知って断念しました。

Pricing – Vercel

同時並行で、データを保管するための DB として Firestore を利用する話がバックエンド側で挙がっていたため、このタイミングでデプロイ先として Firebase Functions / Firebase Hosting が候補に挙がりました。

Next.js の example にサンプルがありそうと判断しこのタイミングで Firebase をデプロイ先に決定しました。

next.js/examples/with-firebase at master · vercel/next.js · GitHub

next.js/examples/with-firebase-hosting at master · vercel/next.js · GitHub

認証技術検証

Firebase Authentication で Twitter 認証し、 Twitter API でツイートの取得を行うことにしたため、フロントから認証情報をどう取得できるのか確認しました。

これはドキュメント通りに組み込むだけだったので、簡単でした。Firestore のルールを設定すれば、認証情報のセッション管理を考えなくていいのが良かったかなと思います。

JavaScript による Twitter を使用した認証  |  Firebase

UI 技術検証

「ぎょうれつや」で一番 UI としてポイントになりそうだった "ミニマップ機能" は先に技術検証しました。

ミニマップは、VSCode のツールや Web ページなどでたまに見る、全体像を見れるようになっている部分のことです。

↓のような感じ。 Minimap - Chrome ウェブストア

ぎょうれつやは、長蛇の列を俯瞰して確認しスクロールやスワイプで行列を移動する、という UI を作り込むため、ミニマップを入れたいと思いました。

スワイプやドラッグによる処理を自前で書かないために、結果としてスマホ環境で一番安定して動いていた react-draggable を採用しました。

GitHub - react-grid-layout/react-draggable: React draggable component

以下が、完成したページです(ちょっとわかりづらいと思いますが...)。

f:id:ka2jun8:20210322113033p:plain

メインの行列とミニマップを用意し、

  • メインの行列は、横長のコンポーネントを用意。

    • 人数分の画像がとにかく横に並ぶコンポーネント
    • 横にスクロールした割合を state に保管する。
    • ミニマップが動かされたら、その割合分左右に scrollTo で動かす。
  • ミニマップは、画面幅を 100% として人数に応じた "表示枠" を用意。

    • 画面内に人数分の画像を詰め込むコンポーネント
    • 表示枠を人数に応じて大きさを変える。
    • react-draggable で掴んで動かせるようにする。
    • 左右に動かされたら、画面幅分の x 軸方向の割合を state に保管する。
    • メインの行列がスクロールされたら、その割合分左右に表示枠を動かす。

横方向のみにしか動かない前提で、細かいサイズやバグは無視してそれっぽく動いた時点で触るのをやめました。

フロント開発でやったこと

  1. フロントから Firestore のデータ読み書き API の開発
  2. ページ/コンポーネント開発
  3. Firebase Functions 開発
  4. 軌道修正とひたすらのバグ潰し

フロントから Firestore のデータ読み書き API の開発

ざっくりと決めた行列(event)のユーザー(user)のテーブルスキーマで、firestore の読み書き API を呼び出すラッパーの開発から行いました。

基本的には、行列とユーザーのデータに対して、

  • リアルタイムなデータ読出を行う subscirbe
  • ドキュメント id を指定した get 。
  • 新規書き込みを行う create
  • 更新を行う update

これらを行うためのリソースラッパーを作りました。コンポーネントやページから呼び出しやすいようにして、型も書きました。これが後で効いてくれたのでよかったです。

削除は、削除したくなったら直接コンソールから消せばいいと思い作りませんでした。

ページ/コンポーネント開発

この間にデザイナーの手によってできていたざっくりワイヤーをもとに、ページやコンポーネントをスタイル無視して、Firestore データの結合しながらページやコンポーネントを作りました。スタイルはあとから直していこうと思いました。

Firebase Functions 開発

Twitter にログインしてユーザーのオブジェクトを Firestore に作ったら、そのユーザーの一番最近発言した Tweet を取得して Firestore に追加する機能が欲しくなり、 Fibase Functions で書きました。

Cloud Firestore トリガー (リンク) が用意されていたので、サンプルを TypeScript 化して functions をぶち込みました。

仕様追加、軌道修正とひたすらのバグ潰し

ざっくり仕様で突き進んだので、途中で何度か仕様追加、スキーマ変更、軌道修正が行われました。 後半は、その変更によるデザイン崩れやバグ対応に追従していました。 あとはどこまで細かい仕様に合わせるかの勝負になっていき、最後は合わせきれない細かい仕様を残し、力尽きて完了となりました。

そのほか

認証とセキュリティ

途中で、レコメンドエンジンによるおすすめの行列一覧を取得するための REST API を作る話が挙がったが、それはやめてフロントのインターフェースを全て Firestore に寄せることにしました。

バックエンドチームは、ユーザーのデータが作られたり、行列に参加するタイミングで作られるデータをもとに、レコメンド検索を行い、別の Firestore コレクションにレコメンド結果を書き込む形にしました。

これにより、フロントは必要なすべてのデータを Firestore から得るようにすることができ、セキュリティは結果として Firestore のセキュリティルールだけ考えればよくなりました。

firebase という選択

firebase init functions , firebase init firestore とするだけで必要なファイルが生成され、すぐにでも動かせるようになりました。

また emulator をローカルで動かせばデプロイしなくても好きにデータをいじって確認できたのはめちゃ便利でした。

ハッカソンという短い期間での開発で、環境としては最適に感じました。

ただし、実際の業務として考えた時、firestore によるデータ管理は SSR に向かないし、データフェッチが安定せず、表示スピードの遅さが目立っていたのが気になりました。

Github Actions でのデプロイ

firebase へのデプロイになった早い段階で、GitHub Actions で firebase のデプロイを行うことにしました。

ハッカソンでデプロイ自動化に時間を割くか迷ったが、最後にバタバタしたときに誰でもデプロイできる状態にしておくことに価値があると思い、パイプラインを用意しました。

結果的に、マーケットにアクションが用意されていたため、速攻で組み込むことができたのでよかったです。

GitHub Action for Firebase · Actions · GitHub Marketplace · GitHub

TypeScript

ハッカソンという短い期間での開発でも、個人的には TypeScript での型は有用だった。 全体として、any を許容し関数の返り値の型はなくても良いルールにしました。とはいえ、any を使うことはほぼなく、型の恩恵に預かることが多かったです。tsc --noEmit でビルドに失敗することが先読みできたのも大きかったです。

ハッカソンはでき初めの頃よりも、後半仕様の追加と変更に追従する場面で、どのコードがどこに依存しているかわかりづらくなる場面で、型によって参照がわかりやすくなっていたのはとても開発体験がかなり良かったです。

特に終盤は眠気などもあり、頭がまともに働いてないのでエディタの方が賢い場面もあったと思いました。

反省点

なし!!

ハッカソンとしては、満点の働き方ができたと個人的には思っています。 ほかのメンバーがとにかく優秀だったので、ぼくの雑なコードに対するキャッチアップもはやかったし、プロマネ、バグ→機能修正や、スタイルあてなど、さまざまなタスクを拾ってもらいつつ、次に進む方向のディシジョンをしてもらえたから自分の反省点がないように見えてるんだと思います。

まとめ

最初「24時間寝ない」ハッカソンに若干抵抗はあったが、結果出てみてとても楽しかった。技術的には firestore のイベントドリブンなシステムに、モダンすぎる勢いを感じられたし、web サービスなんて作ってなんぼっていう感覚を思い出せたのはとてもよかったです。

ただ、完徹でサービス作るのは、あと数ヶ月はいいかなって思いました。