【個人開発】文学作品の冒頭を読んで作者を当てる Web サービスを開発しました
文学作品の冒頭を読んで作者を当てる Web サービス、「文豪推理」を友人の @kokokocococo555 さんと開発しました。
文豪推理問題
Twitterシェア時の OGP 表示
ユーザー登録などはなくすぐ遊べるので、https://bungo-suiri.tailoor.dev からぜひ遊んでみてください!
2人とも本職 PG ではなく、試行錯誤しながら開発しました。
初めてのまともな個人開発で学びが多くあったので、サービス紹介と経験の還元を目的として本記事にまとめます。
どんなサービスか
文豪推理は、日本文学作品の冒頭を読んで、「どの作者が書いたものか」を 4択で当てるクイズです。
回答すると正解・不正解と共に、正解の作品の出版年や作者情報などが表示されます。
また回答後の「問題共有」のツイートボタンを押すと、その問題に挑戦できるリンクと、問題文が書かれた OGP 画像をシェアできます。
問題となる作品冒頭の文章や、その底本、出版年、作者情報などは青空文庫から取得しています。
なお、リクエストのたびに青空文庫に負荷をかけるようなことがないように、データはサーバーに保存して、それを参照するようにしています。
2人とも思った以上に歳を取り、本と言えば技術書ばかりで、昔はあんなに読んだ小説を読まなくなったな......という悲しみが初めのきっかけでした[1]。
同じような感覚を持つ方は、ぜひ文豪推理に挑戦して、小説の手触りを懐かしんでいただければと思います。
また、今も小説を読む方にとっては力試しや新しい作品の発見の場に、小説をそもそもあまり読まない方にとっては新しい世界に触れるきっかけになれば嬉しいです。
利用技術と構成
利用技術一覧
文豪推理は、以下の技術を主に利用しています。
- TypeScript
- React
- CSS Modules
- Python
- Next.js
- Cloud Build
- Cloud Run
- Cloud Storage
- Vercel
役割分担
2人の役割分担です。
今回は技術面では私の担当が多かったので、執筆の都合上私のアカウントで記事投稿をしています。
- @kokokocococo555
- サービス原案
- 問題データ作成・OGP 画像作成 (Python)
- プロジェクト管理
- ドメイン取得、OFUSE 利用や金銭管理など
- @kangetsu_121
- フロントエンド (+ API) 実装 (TypeScript, React, Next.js, CSS Modules)
- インフラ構築 (Cloud Build, Cloud Run, Cloud Storage, Vercel)
- 共同
- サービス具体案
- サービスデザイン検討
- サービステスト
- デバッグ
- etc.
構成詳細
フロントエンド: TypeScript, React, CSS Modules
ページ描画をはじめ、正解・不正解判定、回答したか否かに応じた作品情報の表示などの処理は、React で行っています。
React は、はじめ JavaScript で書いていたのですが、途中で TypeScript に移行しました。
ESLint や TSConfig を設定することで、開発中に VSCode でしっかり警告を出してくれるので、それらに対応するだけでより堅牢なコードになっていったと思います。
また、CSS は CSS Modules を使って記述しました[2]。
CSS を少し触った程度の自分には、CSS からほぼ書き方も変わらず、かつスコープを分けることでグローバルな CSS の衝突をほぼ気にしなくて良くなる CSS Modules は、メンテナンス性も高く非常に便利でした。
何より、コンポーネントごとに CSS ファイルを分けられるので、React での開発と相性が良いと感じました。
なお、より具体的には CSS Modules の SCSS 記法版を採用して、階層的な記述や変数利用などもできるようにしています[3]。
使ってみたいと思った方は、catnose さんの Next.jsにCSS Modulesを導入する の記事が非常に参考になりますのでご覧ください。
バックエンド: Next.js
フレームワークには Next.js を利用し、Dynamic Routes を利用して問題ごとに異なる URL を割り当てることで、シェア時の個別の OGP 表示や、シェアされた URL から同じ問題に挑戦できる機能を実現しています。
また、OGP 生成のため、getStaticProps
, getStaticPaths
を利用して SSG (Static Site Generation) を行いました。
ローカルでのプレビュー環境の立ち上げがとても早く、かつホットリロードで大抵のソースコードの変更をすぐ反映してくれるので、開発体験は非常に良かったと感じます。
他にも、Next.js の API Routes を利用して、ランダムな問題の選択、回答ログの送信などを実現しました。
ただし Next.js に関連してはまったポイントはいくつかあるので、つまづいた点とその解決でもう少し詳しく解説します。
問題データ作成・OGP 画像作成: Python (文責: kokokocococo555)
文豪推理で出題される問題は青空文庫様のテキストデータ及び「作家別作品一覧拡充版」を用いて作成しています。
このテキストデータは青空文庫形式でルビや記号などが含まれており、それらを除外する前処理が必要でした。前処理を行ってから冒頭400文字程度を抜き出し、問題文としています。
選択肢は正解作家とその他の作家の中からランダムに選んでいます[4]。
各問題の OGP 画像には少し気持ちを込めておりまして……。SNS 上で拡散・アクセスしていただきたいので「SNS シェア時の OGP 画像に問題を記載し、SNS 上で URL アクセス前から興味を持ってもらう・問題に挑戦しようと思ってもらう」ようにしました。
全ての問題の OGP 画像を前もって生成する必要があったので、「Pythonで画像を編集したり、文字を入れたりする」を参考に、Python の Pillow ライブラリを用いて一気に生成。要素のレイアウト等、手動での試行錯誤が必要で、文字サイズや位置などの見た目が心地よくなるようになるまでなかなか大変でした。
インフラ: Cloud Build, Cloud Run, Cloud Storage
デプロイ先のインフラとしては、GCP の Cloud Run を利用しました。
GitHub と連携させることで、ビルドからデプロイまでが自動で行われるようにしています。
具体的には、Dockerfile を含む GitHub リポジトリに Push をすることで、同じく GCP の Cloud Build を通してコンテナのビルドが行われ、そのコンテナが Cloud Run にデプロイされる、という流れです。
また、OGP 画像の格納・参照先として Cloud Storage を利用しました。
SSG 時に OGP として Cloud Storage 上の対応する OGP 画像を参照することで、問題ごとの OGP 生成が可能になっています[5]。
開発環境: Vercel
本番インフラでは Cloud Build -> Cloud Run を利用すると書きましたが、開発時にも逐一 push のたびにコンテナの build -> デプロイが走っていたのでは時間効率も悪く、料金もかかってしまいます。
このため、main ブランチマージ時以外は、Vercel にプレビュー環境がデプロイされるようにしました[6]。
コンテナのビルドなども走らないためプレビュー環境の作成は圧倒的に早く、複数人開発においてレビューが迅速に回せる要因の 1つとなったと思います。
なお、そのままでは main マージ時に本番環境が Vercel にデプロイされてしまうため、main マージ時はデプロイしない、という設定を Vercel のドキュメント: How do I use the "Ignored Build Step" field on Vercel? に従って行っています。
各技術の選定理由
各技術の選定理由ですが、「自分達が使える・使いたい」以外の理由は、主なものは次の通りです。
Cloud Run
Cloud Run はコンテナベースの技術で、リクエスト増減に応じて自動でスケールアウト・スケールインしてくれます。
つまり、リクエストが少ないときはコンテナの数を減らしてコストを下げ、多くなったらコンテナを自動で増やして負荷を分散してくれます。
使われていないときは低料金で使えること、リクエストが思ったより増えたりバーストしたときに自動でスケールアウトして対応できることを念頭に選びました。
問題データのローカル保存
青空文庫から抽出したデータの保存先として、素直に考えると RDBMS や KVS が思いつきました。
しかし、今回はそこまで大きくないデータであったこと、またデータは読み取りのみで書き込みが発生しない、検索なども行わないため、データロックや検索最適化が不要と考え、シンプルに JSON ファイルをサーバーに置くことにしました。
ただし、リクエストが増えた時に考慮していない問題が起きたり、今後問題を増やした時にスケールさせるために、データベース管理システムなどの導入を検討することはあり得そうです。
また、ビルドサイズも増えてしまうので、CI の時間が無視できないほど延びた時も再検討が必要そうです。
開発での学び
ここからは、初めての本格的な個人開発の中で得た学びを紹介します。
良いと感じたもの
まず、開発中に良いと感じたもの、次の開発でも活かしたいものなどを紹介します。
GitHub での Issue ベースでの開発
今回は冒頭述べたように 2人で開発していたのですが、開発タスクは GitHub の Issue を使って管理していました。
2人で開発をしていて、思いついた機能拡張や発見したバグなどをどんどん溜めていき、Issue の中で議論や調査結果を集約できて、非常に便利な仕組みだなと改めて思いました。
また、Issue をクローズする Pull Request を発行した時に Close
, Resolve
などのキーワードとともに #
で Issue 番号を参照することで、関連付けおよびマージ時の自動クローズができる機能も便利に使いました。
(当たり前の機能ですが) クローズした Issue の数がパッと見えるのも意外にモチベーションや自信につながりました。
タスクの共有・可視化・情報集約が非常に効率的で、GitHub よくできてるなと改めて思い知らされました。
週次ミーティングでのメリハリ
毎週時間を決めて定例ミーティングをしていました。
当然どちらも本業があるので、時間は水曜日の 22:00 からとしていました。
これがかなりモチベーション維持やプロジェクト継続に寄与したと感じており、毎週忙しくても 10分でも時間を作って話すことで、プロジェクトの自然消滅を避けられたと思います。
本業の傍らだとどうしても、「本業が忙しい」「プライベートが忙しい」などを理由に、なんとなく開発が滞って自然消滅する、と言うことはよくあります (実際過去ありました)。
また、簡単ではありますが議事録を取って、その数が増えていくことでも、進捗や「よく頑張った」感が出て、これもモチベーションや自信になったと思います。
サービスロゴの作成
とても素敵なサービスロゴ及びファビコンですが、こちらは共通の友人である @honey821_gtr さんに作ってもらいました!
開発者 2名はデザインはやったことがなく、はじめはロゴなしで開発を進めていましたがやはりトップ画面などに寂しさはありました。
honey821_gtr さんが素敵なロゴを作ってくれたことで、サービスの顔ができ、そこで「ちゃんとしたサービスに見える」ようになったと思います。
改めてこの場を借りて、ありがとうございました!
2人での開発・パイロットテスト
1人でなく、2人で開発したことで、より品質の高いサービスに近づけられたと思います。
今回のサービスは商用のものでもなく趣味の延長だったこともあり、どうしても自分で実装した部分は妥協が入ったり、バイアスがあって問題に気づきにくくなることもありました[7]。
基本的にはお互いに Pull Request のレビューをし合っていましたが、「言われてみれば確かに」と思うような指摘をされることが何度かありました。
また、ある程度動くようになった段階で友人にパイロットテストとして触ってもらったのですが、2人では気づかなかった気になる点など教えてもらい、Issue をすぐ立てて対応しました。
このように、1人より 2人、そして周りの人に事前にパイロットテストをしてもらうことは重要だと実感できました。
react-icons
これは具体的なライブラリの話ですが、React での開発時にアイコンを使いたい場合は react-icons が非常に便利でした。
初めは Fontawesome を使っていたのですが、Fontawesome よりも少なく直感的な記述でアイコンを利用できます。
import { FaTwitter } from 'react-icons/fa';
// ...
<FaTwitter /> つぶやく
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTwitter } from '@fortawesome/free-brands-svg-icons';
// ...
<FontAwesomeIcon icon={faTwitter} /> つぶやく
かなり多くのアイコンが存在するので、使いたいアイコンは大抵存在すると思います。
ただし、Fontawesome にしかないアイコン (私が見つけたものだと例えばアルファベット) などもあったりと、差異はあります。
つまづいた点とその解決
次に、開発中につまづいた・はまった点などを紹介します。
こちらは主に技術面のお話になります。
OGP の生成
おそらく多くの人が 1度はハマるのではないかと思いますが、私もはまりました。
初めは何も意識せず、Dynamic Routing で生成する各問題ページに固有の OGP を指定していたのですが、試してみると OGP が表示されません。
これは、Twitter のクローラー API が JavScript を解釈できないためで、対応するにはあらかじめ OGP 情報などを含んだ HTML を生成しておく必要があります。
このため、バックエンド: Next.js で書いたように、Next.js の機能で SSG をしておくことで対応しました。
この OGP 問題は多くの解説記事がありますので、以下などもご参照ください。
M1 Mac (arm64 multipass Ubuntu) での amd64 イメージのビルド
私は開発マシンとして M1 MacBook Pro に multipass Ubuntu20.04 を立てているのですが、Docker Build ではまりました。
M1 Mac 及びそこに立てた multipass Ubuntu は、CPU アーキテクチャが arm64 です。
一方で、例えば Docker Hub などのコンテナレジストリには amd64 のコンテナが多く、arm64 のマシンではそのまま使えないことがほとんどです。
「Cloud Run を利用しよう」と考えた時点でコンテナの作成は必須だったのですが、この問題があったために素直に docker build
をしただけではアーキテクチャ違いのコンテナがビルドできず、ローカルでの検証が非常に手間取りました。
この問題は、docker docs や Run AMD64 Docker Images On An ARM Computer などを参考にアーキテクチャーエミュレーターイメージ を利用して、マルチアーキテクチャビルドを可能にして docker buildx
コマンドで解決しました。
今後も同じ問題に遭遇しそうなので、別途ちゃんと手順や理屈をまとめておきたいと思います。
連続した記号が折り返されない
クイズの文章である、小説の冒頭は青空文庫から取得しています。
その中に、こんな書き出しのものがありました。
.......................................................
そしてこの問題を表示すると、なんと以下の画像のようにカードから問題がはみ出てしまっていました。
バグによりはみ出た問題
これは、最近「【CSS】文字列を改行させるプロパティの挙動比較」 の記事などでも紹介がありましたが[8]、CSS の overflow-wrap
の挙動の違いによるものです。
主要ブラウザの最新版では挙動が有効になっていることを確認した上で overflow-wrap: anywhere;
として解決しました。
余談ですが、Internet Explorer のサポートが 2022年6月に終了したことで、大手を振って (?) いろいろな機能を有効化できるようになったのはありがたいですね。
Cloud Run にログを送信する
今後のサービス改善や別サービス開発のために、「ユーザーがどの問題にどのように回答したか」といった回答ログを集めておけるようにしました[9]。
この時、Cloud Run のドキュメントを見ていたら、「標準出力にログを出すだけで GCP にログが送られる。JavaScript なら console.log()
を使えば良い」と言う記述 があったので、回答情報を集約して console.log()
で吐くようにしました。
しかし、ブラウザの console を見るとちゃんと意図した構造でログが出ているのですが、GCP には一切送られている気配がありません。
ものすごく悩み、日英で検索をたくさんしたのですが、同じように悩んでいるものすら見つかりません。
結局いろいろなところに console.log()
を仕込んで、どこに仕込んだものが GCP に送られているか、などを調査した結果、「クライアントサイドではなくサーバーサイドで console.log()
したものが GCP で収集できる」ということに気づきました。
わかってしまえば当たり前のことで、クライアントサイドで吐いたログがサーバー側に送られるわけないのですが、ブラウザ環境とサーバー環境の JavaScript の区別すら混乱して意識できていなかった私は非常に悩んでしまっていました。
結局、Next.js にログを受け取って出力する API を作ることで解決しました。
検索時は同じ悩みが全く見つからなかったのですが、これも迷う人はいるのではないかと思うので、後で別途記事にまとめようと思っています。
支援プラットフォームの選定
今回インフラに Cloud Run などを利用していると書きました。
また、カスタムドメインも取得しているので、それらの維持費用もかかっています。
こうした運用には金銭的コストがつきものなので、支援プラットフォームを利用したいと話し合い、最終的に OFUSE をいう国産サービスの利用を決定しました。
実は初めは Buy Me a Coffee を導入しようとしていました。
海外の大きな有名サービスで利用実績も十二分にあり、手数料等も小さかったためです。
ただ、検証を進めていたら、いくつか私たちにとっては気になる挙動があり、断念しました。
具体的には、
- 支援してくださる際に支援者が入力した Email アドレスで勝手にアカウントができてしまう (アカウントなしでの支援ができない、情報が自動登録されてしまう)
- クレジットカードの中には決済時にクレジットカード会社から「不正利用ではないか」という警告が出たケースがあった
などが主な理由でした。
代替サービスを探していて、OFUSE を見つけ、
- 国産サービスで、東京都の起業支援事業「アクセラレーションプログラム」の 2017年募集期に採択され、これまでに実績も積んでいる
- クリエイターを応援する姿勢に共感を持った
などを決め手に、利用させていただくことにしました。
ほぼ全ての開発者の夢だと思いますが、運用費用を超えて副収入にもつながるといいな、と思っています[10]。
開発にあたって参考になった書籍・Web ページなど
私が主にフロントエンド開発を担当しましたが、職能的にフロントの経験は現職でも積めていないので、自習しました。
フロント素人が本記事で紹介した技術スタックで開発するにあたり、参考・お世話になった書籍や Web ページを紹介します。
「別言語でちょっとプログラミングをやったことがある」人にはマッチすると思いますので、参考になれば幸いです。
- JavaScript Primer: JavaScript の基礎を解説してくれます。ただしブラウザ環境などの解説はなく、共通仕様である ECMA Script にフォーカスしています。ECMA Script の仕様変更に追随して継続的に更新されているので、情報が古びることがほぼありません。
- りあクト!シリーズ: つい先日の 2022/9/8 に、最新第4版も販売された大人気の同人誌です。メンターとメンティーの対話形式で全編書かれており、非常に読みやすく、かつ詳細な解説が盛り込まれています。個人的には React に関わる歴史の話などが詳しい点が大好きです。
- React 公式ドキュメント: React は公式ドキュメントが読みやすかった記憶があります。初めて触る方はチュートリアルをやってみるのが良いと思います。
- Next.js 公式ドキュメント: こちらも公式ドキュメントがわかりやすかった記憶です。最低限はカバーできると思います。
- プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで: いわゆるブルーベリー本です。本記事執筆時点でまだ途中までしか読めていないのですが、もっと早く読んでおけばと思っています。丁寧な解説と詳細をカバーするふんだんなコラムで、歴史的背景などもおさえつつ、わかりやすく TypeScript を学べる良い本ではないかと思います。
今後の展望
初めてまともに完成したサービスなので、勉強も兼ねて今後も開発を継続できたら良いなと思っています。
問題数を増やしたり、海外作品も対象にしたりと、単純な機能追加だけでもいろいろできそうです。
また、今回で kokokocococo555 さんとサービスを実現できたことを自信に、今後も別のサービスも創っていきたいと思っています。
またサービス紹介の記事でなど報告できるよう頑張りますので、サービスを触ったり Twitter やフォームで感想・要望などを書いていただいけると大変励みになります!
私もずっとそうでしたが、個人開発に迷っている方は是非なんでもいいので創ってみてください。
インプットするだけでなく、ものを作ってアウトプットすることが本当に勉強になったし、何より形になるのはとても楽しかったです。
-
非常にどうでもいい話ですが私のユーザー名 knagetsu も、『吾輩は猫である』の登場人物である水島寒月から取っている程度には本が好きです ↩︎
-
CSS Modules はメンテナンスが停止しているという話が出ており、将来性が危ぶまれていましたが、実装は継続してメンテナンスされていたりと、利用可能性自体は落ち着いてきている印象があります。参考: Next.jsにCSS Modulesを導入する, CSS Modulesの歴史、現在、これから ↩︎
-
近年は CSS 自体がどんどん便利になっており、SCSS (Sass, Less) の特徴だった変数の利用や階層的な記述も可能になった・なりつつあるようで、いずれ CSS Modules で全て良くなるのかもしれません。 ↩︎
-
将来的にはより面白い問題となるように検討が必要です ↩︎
-
初めは Cloud Run の各コンテナから Cloud Storage をマウントさせて……など考えていましたがうまくいかず、かつ普通に OGP では画像 URL を参照すれば良いことに気付いたのでこの構成にしました。 ↩︎
-
ただし Hobby Plan を利用していたため、@kokokocococo555 さんは Vercel そのものへのアクセス権はない状態で開発していました。プレビュー環境は Unlimited Previewers なので、それで Vercel へのアクセス権がなくても状況を確認できていました。 ↩︎
-
もちろん本業でこうした妥協はしていません、念の為。 ↩︎
-
すごいタイミングよく自分がはまった話題の記事だったので、記事にコメントもさせていただきました ↩︎
-
Data Analyst をやっている kokokocococo555 さんが強く希望していました ↩︎
-
著名 OSS ですら難しい昨今なので現実は厳しいとは思っていますが、夢を持っていきたいですね ↩︎
Discussion