この記事は新歓ブログリレー6日目の記事です。
こんにちは! この記事では、今年の冬ハッカソンで開発した合宿総合アプリ『rucQ』について紹介します。冬ハッカソンは他にもとんでもない作品が並んでいますので、ぜひ他の作品のブログも見ていってください!
traPの合宿
そもそも科学大デジタル創作同好会traPにおける合宿とはどんなものなのか、と疑問に思う方もいらっしゃると思います。みんなで宿に泊まり込んでエナジードリンクを片手に創作や開発に没頭……というわけでは決してなく(そういう人がいないとは言わないけど)、traPの合宿は合宿とは名ばかりの単なる団体旅行です。昨年の夏合宿についてはAlterさんの記事で詳しく紹介されています。今回の冬合宿では約100人の参加者一同が観光バスで草津に向かい、2泊3日の期間でスキーや温泉を満喫してきました。

traPの合宿を統括するのは合宿係と呼ばれる3人の部員です。当日のアナウンスや誘導のみならず、合宿のしおりの執筆、参加費の徴収、宿の予約、出欠管理、フィードバックの募集に至るまで合宿に関するあれこれを一手に担うスーパーエリートな方々です。冬ハッカソン18班のメンバーの中ではかぜみやおぐさんが合宿係をされていて、彼が提起した「合宿の統括をもっと楽にする」という課題が今回の開発のきっかけになりました。
rucQの機能
rucQは合宿総合アプリです。以下に示したような機能が実装されています。
- 合宿のしおり ... 合宿における留意点を合宿係がまとめたテキストが表示されます
- スケジュール表示 ... 合宿中の予定や企画されているイベントの情報を一覧できます
- ユーザー情報 ... 自分が泊まる部屋の番号をはじめ、参加者が自身の合宿のオプションを閲覧・編集できます
- 部屋情報 ... 合宿参加者が宿泊中の部屋が見やすく表示されます
- 管理者ツール ... 合宿係が参加者各個人の情報をはじめ、rucQに表示する色々な情報を閲覧・編集できます

コンセプトと命名
当初こそ「合宿係の仕事を楽にする」ためのWebサービスを開発するつもりで企画を進めていましたが、チーム内での話し合いの結果、以上で紹介したように「合宿係と参加者の双方にとって便利な合宿総合管理アプリを作る」という構想がまとまりました。これは次のようなメリットを意識してのことです。
- 合宿参加者の視点でも、専用アプリによって情報伝達が改善されれば嬉しいことが複数ある
- 参加者に合宿総合アプリを使ってもらうことで、合宿係としても参加者の情報をまとめて管理・閲覧しやすくなる
合宿に際してやり取りされる情報の一例として、自主企画イベントに関する告知があります。合宿ではイベントが目白押しです。合宿自体の予定に組み込まれているメインイベント(冬合宿でいえばスキーやらん☆ぷろ)はもちろんのこと、目的地に到着したその日から最終日の朝まで合宿参加者による自主企画が立ち並びます。前回の夏合宿までは、これらのイベントに関する告知や参加者募集は部内SNSサービス『traQ』の特定チャンネルでなされてきました。しかしあまりにも数が多いため、参加したいイベントについての告知をtraQで探すのには少々手間がかかります。合宿というせっかくの機会を存分に楽しむために、合宿で開催されるイベントを一覧できるような場所があると嬉しいに違いないと考えました。先ほどの紹介における『スケジュール』画面はこのような動機で実装されています。
サービスの名前は「リュックサック」からとっています。「リュックサック」は英語で「rucksack」と綴ります。遠出を連想させるリュックサックは合宿のよいシンボルになるだろうと考え、そして後ろの1文字を大文字のPやQにするというtraPの慣習に倣い、開発するサービスを『rucQ』という名前に決めました。ロゴは大文字の『Q』をファスナーに見立てた意匠です。

余談ですが、命名経緯を踏まえると「rucQ」は「リュック」……なのですが、実際に合宿で使ってもらったところ「ラック」と呼ぶ人が多数派でした。とくにこだわりもないので好きなように呼んでもらっています。「rucksack」は英語としては「ラックサック」に近い発音をすることを踏まえると、もしかしたら「ラック」と呼ぶ方が正しいのかもしれません。
フロントエンド(文責:きつね)
フロントエンドではViteが提供する環境とテンプレートを基礎としてVue.jsによる開発に取り組みました。複数人による開発になるので、事前にFigmaというデザインツールの上でデザインを共有し、開発のスムーズな進行を図りました。デザインは主に私きつねが担当しています。テーマ色を明るいオレンジ色とすることが決まり、合宿らしく鮮やかに、スタイリッシュというよりは遊び心を感じるようなデザインを心がけました。

Vuetify
フロントエンド開発で特筆すべきことがあるとすれば、UI設計にVuetifyを導入した点です。Vue.jsによる開発では開発者が「ある決まったデザインのテキスト入力欄」「アイコンとテキストが並んだフッターのボタン」などのコンポーネントを設計し、それを階層的に画面に配していくのが基本です。これらのコンポーネントを自ら作っていくことも珍しくはないですが、スタイリッシュなコンポーネントのサンプルを取り揃えたVuetifyと呼ばれるライブラリを活用することで、見映えのする画面をより少ない時間で書くことが可能になります。
個人的にVuetifyを使っていてよかったと感じた瞬間のひとつは、イベントを登録する画面で開始時刻を設定するためにこんなUIを取り入れたときのことです。

アナログ時計風の円盤上をドラッグすることで時刻を設定できるようなUIです。モバイル版Googleカレンダーにおける予定の時刻選択でもよく似たUIが採用されています。
この端正なダイアログもVuetifyで予め用意されていたコンポーネントのひとつ(Time Picker)です。もし自力でこれを実装しようとしていればデザインの調整やバグの修正に途方もない時間が溶けていただろうと思います。あるいは端から諦めて数値入力欄で済ませてもよかったでしょうが、少なくとも私はこのUIが「時間」を入力することの直感的な分かりやすさを大切にするとてもよい選択だと考えました。
PWA化
PWA(Progressive Web Apps)とは、一般に単一のWebページにアプリケーションのような機能性(たとえばオフライン動作やプッシュ通知)を持たせたものです。サービスを開発するのに適した言語はそのサービスが置かれるプラットフォーム(OS、Web)によって異なります。たとえばブラウザで開くWebアプリを作るならば開発にはJavaScriptやTypeScriptを用い、iOSにインストールされるアプリケーションとして開発するならばSwiftを用いるのが一般的とされているようです。マルチプラットフォームなアプリの開発では、たとえそれぞれでほとんど同じようなUI(見た目・機能)を用意したくても、一方の言語で書き上げた画面を異なる言語で改めて書き直すことになります。
この手間を回避するためにWebページそのものをインストール可能なアプリとして扱う近道が用意されていて、この近道を通って出来上がったアプリをPWAと呼びます。PWA化したWebアプリは、その内部(正確にはブラウザの内部)にキャッシュとして適切に情報を蓄積しておいて、オフライン状態でもその情報をもとにページを表示することができます。
rucQは主に合宿という出先で使うアプリであり、インターネットの回線が遅かったり、そもそも繋がらなかったりする場所で開くことが大いに考えられます。そのような場合でもキャッシュに保存された情報を頼りにスケジュールや合宿のしおりを表示できれば、このアプリは合宿参加者にとってより役立つものになります。rucQはVite PWAと呼ばれるプラグインを用いてPWA化されていて、キャッシュを駆使して迅速かつタフに情報を表示させることができるようになっています。目的はスキーゲレンデのようなインターネット接続が不安定になりがちな場所で情報を表示させることでしたが、副次的な効果としてインターネット接続の有無によらず表示が高速になるというメリットもあったようです。冬合宿後のフィードバックでは宿の中でもより素早く情報を閲覧できてよかった、という意見が寄せられました。
API接続
昨年の1-Monthonにて私が開発に携わったleaQというWebアプリでは、バックエンドにリクエストを送信して情報を受け取るための関数(APIクライアントと呼ばれる)を書くためにfetch
関数をそのまま用いていました。しかしこの方式には「用意されているAPIごとに関数を手書きするのが面倒」「型安全性に欠ける」などの欠点があったので、rucQのAPIハンドラはopenapi-typescript
によって自動生成された型をもとに、openapi-fetch
によって簡易化されたハンドラをそのまま用いることにしました。
APIとはフロントエンドが「どこに(エンドポイント)どんな方法で(HTTPメソッド)」アクセスしたらバックエンドが「何を返す(レスポンス)」という約束事の集まりであり、バックエンドはこの約束事を守るように設計し、フロントエンドはこの約束事を信頼してユーザーに表示する画面を作っていきます。その仕様の全てはopenapi.yaml
というファイルに書かれています。上に挙げた2つのライブラリはopenapi.yaml
から型や関数を自動生成してくれるもので、フロントエンドにAPIの仕様を一つひとつ読み解いてバックエンドに働きかける関数を手書きで用意する手間を省くことができます。
バックエンド(文責:あきも)
元々バックエンドは僕ときつねくんの2人でやる予定だったのですが、なんやかんやあってほぼ1人で開発しました。実際rucQの便利さを決めるのはフロントエンドでの情報の見せ方や操作性で、バックエンド側はデータベースのラッパーでしかないのでフロントにリソースを割くのは間違っていなかったと思います。
設計
ハッカソンの制作物はグチャグチャなコードでもとりあえず動いていればヨシ!になりがちですが、rucQは合宿で使うためのアプリという性質上長期的にメンテナンスしやすいものにすることが求められます。このあたりの知見は全く持っていなかったので手探りでしたが、同じGolangでクリーンアーキテクチャライクなtraQを参考にした、いわば「クリーンアーキテクチャライクライク」な設計になりました。もはやクリーンアーキテクチャではありません。数年後に見たら書き直したくなるかもしれませんが、今のところはそこまで悪くない設計だと思っています。
OpenAPIからのコード生成
1-MonthonのleaQのときは途中までAPIドキュメントの有用性を軽視していて、開発期間後半からバックエンドの実装を基にOpenAPIを書き起こすという苦行を行っていました。Goの構造体の定義を見ながらOpenAPIに同じものを書き、変更するときは両方更新するというのを、数回ならまだしも毎回やるのは結構面倒です。ということで、(SysAd班の仕事でちょっと触れたのもあって)スキーマ駆動開発をすることにしました。OpenAPIからのコード生成にはoapi-codegenを使用しています。
ところで、leaQのときはGORMのモデルをそのままAPIレスポンスとして返していました(よくない話!)。今回のようにOpenAPIからコードを生成すると必然的にレスポンスの型とモデルが別々に定義されるので、意図しないデータがインターネットに放流されるリスクを軽減できるというメリットもあるんだな〜と感じています。同じような型を2つ使い分けなければいけないというデメリットはありますが、jinzhu/copierで機械的に変換できるので苦にはなりませんでした。
ここまでコード生成して良かったことを書いてきましたが、OpenAPIのoneOf
から生成されたコードは……う〜んという感じでした。

これはGolangの問題で、oapi-codegenはまぁ使えなくはないコードを出してくれるので致命的なものでもないのですが、でもフロントエンドがTypeScriptなら同じ柔軟性は欲しいよね〜と思ってしまいます。ちなみにtraP代表のしーぴーさん(@cp20)もGoにユニオン型がないことにキレていました。
今後の展望
現在のrucQは最低限の機能のみを揃えた発展途上のアプリケーションです。企画段階では雑談用掲示板や参加者個人用メモエディタ、点呼などといった機能の実装も検討されていましたが、これらの機能については実装が間に合わない可能性があり、また需要の存在が確かでなかったことから冬合宿前の実装を見送りました。合宿後にいただいたフィードバックにて開発チームが考えつかなかった機能の要望なども寄せられ、合宿参加者のニーズがより詳らかになってきました。
rucQの次の出番は9月頃の夏合宿です。安定版となったrucQがtraPの合宿でより様々なことに役立つように、次の合宿を見据えて開発を進めています。開発に協力してくれる新規メンバーも募集中!
感想
kitsne
リーダーとデザイン・フロントエンドを主に担当しました。最初はバックエンドをメインにやることになっていたのですが、デザインを担当した縁でフロントエンドに色々と首を突っ込んでいるうち気付いたら己の持ち場に手が回らなくなり……(あきもくん、ごめん)。開発期間が始まる前に要件とデザインはかなり固まっていたので難なく完成させられるだろうと思っていましたが、蓋を開けてみれば困難の連続でした。予定より随分と遅れてしまいましたが、無事に合宿に間に合ってよかったです。この前の1-Monthonではフロントエンドの大部分をワンオペで対応しましたが、今回はフロントエンドを複数人で協力しつつ進めていくことになり、技術の活用方法というよりチーム内のスムーズな連携の面で学べることが大きかったなと思います。合宿に参加したメンバーにフィードバックをいただいて、具体的な役割を担うサービスを開発することのやり甲斐と責任とを実感するよい機会になりました。チームメイトのみんなに感謝!
akimo
バックエンド、API、データベースの設計から実装まで(ハッカソン期間中は)だいたい1人でやりました。Golangはそこそこ書ける方だと自負しているので最初は1人でも余裕だろうと思っていましたが、長期的な運用を考慮しながら設計するのは想像以上に難しかったです。「コードが書ける」ことと「設計ができる」ことは別の能力なんだなぁと痛感させられました。でもまともに設計を考えたのが初めてなのにしては結構頑張ったんじゃないでしょうか。かなり多くのことを学べたので参加して良かったです。
ただ、こんなにrucQの開発に携わったにも関わらず僕は冬合宿に参加していません(悲しい)。「ものつくり」という、スターリングエンジンを作る集中講義と日程が被っていたためです。日程が被っていることは知っていたのですが、ものつくりを履修できるのは抽選で通った16人だけなのでどうせ通らないだろうと思って軽い気持ちで申し込んだら通ってしまいました……。良い授業ではあったので(コミュ力のない僕にはきつかったけど)取って良かったと思っています。でも合宿にも行ぎだがっだ!!ちなみに、スターリングエンジンはこんな感じです。

僕はあまり工作の方には貢献できませんでしたが、掲示用のポスターを作りました。ものつくりセンターに行くと見られるかもしれません。
mumumu
どうも!!mumumuです。rucqでは主にフロントエンドを担当していました。
trapに入ってもう一年が経とうとしていて今回は三回目のハッカソンでした。
夏のひと月のハッカソンではバックエンドを担当したのでrucqではフロントをやることにしました。「いろんな機能が欲しいねー」とみんなで話していたら作るページが膨大になって大変でした(笑)。一応ハッカソンの期間は一週間なのですがそれでは終わらず、合宿前日まで開発していたので結局開発期間はひと月くらいになりました。
前回のハッカソンも必死にコードを書いていたのですが今回も同様とにかく必死にコードを書いていました。少し汚く書いてしまった自覚があります。
バックエンドはakimoくんが主に一人で担当していたのですが少しだけ僕が書いたときはakimoくんの整理されたディレクトリ構成やコードの書き方に驚かされました。(めちゃめちゃプルリクにレビューしてもらった)
開発が少し落ち着いて最近、そろそろ動くだけでなく細かくコンポーネントに分けたり、使いやすく読みやすいようにコードを書けるようになりたいなあと思ってきました。今後の目標ですね。
合宿で実際に皆に使ってもらえたみたいで嬉しかったです!次の合宿までにもっと機能を改善して最強のアプリにするぞ~
ogu_kazemiya
フロントエンドを担当しました、かぜみやおぐです。
私はtraPで合宿係を務めていて、常々「この仕事めんどくせ~」と思っていました。
今回rucQを作ったのは、この仕事を楽にしたいというモチベーションからです。
結果的に合宿係の仕事を自動化するだけでなく、合宿参加者にも便利なサービスが作れてとても満足しています!
私は去年の春ハッカソンでもwebアプリを制作したのですが、そのときはフロントエンドの勉強を始めたばかりだったのもあり足を引っ張るだけでした。
今回はそのリベンジと思って臨んだハッカソンだったので、設計から実装までしっかり貢献できてよかったです。
合宿直前の開発は任せきりになってしまいましたが……(きつねくんありがとう…!)
rucQの開発は今後も続く予定です。
プロダクトとして一旦完成はしていますが、まだ追加したい機能はあります。
100人に使ってもらえるプロダクトを作る機会はそうそう無いので、更に良いものを作れるように頑張ります!
おわりに
ここまで読んでいただきありがとうございました。traPは何かアプリやプロダクトを開発したい!!と思い立ったとき、それをサポートしてくれる環境や機会がめちゃくちゃに充実しまくっている場所です。Web開発やGitの基礎講習会があり、ハッカソンですごい仲間に助言をもらうことができ、Webアプリを動かしておけるサーバー、そしてそれを簡便に扱うためのプラットフォームまで綺麗に整備されています。きっとあなたもtraPに入ったら自分が作りたいものに専念できることの素晴らしさを実感するはずです。
新歓ブログリレー、明日の担当は@zoi_dayoさんです。お楽しみに!