はじめに
サイクリング中の事故低減を目的に、サイクリングの事故やトラブルがあった地点を見える化し集約するサービス「MAAKS(マークス)」をリリースしました。
この記事では Web アプリの概要や使い方、技術スタックやこだわった点、また開発過程での苦労や学んだこと、さらには今後の展望まで、詳しく解説します。
サービスURL
リポジトリ
https://github.com/YukiWatanabe824/MAAKS
自己紹介
こんにちは、渡邊有喜(わたなべ ゆうき)と申します。
自転車メーカーに勤める傍ら、プログラミングスクール「フィヨルドブートキャンプ」でプログラミングを学び、今回紹介するサービスを開発しました。
記事を書いている段階では卒業目前、卒業後はWEBエンジニアを目指しています。
サービスの紹介
サービスの紹介に入る前になんでこのようなサービスを作ったか、について話したいと思います。
サイクリングは楽しい、でも…
私は現在自転車メーカーに勤めています。大学の頃は自転車サークルに所属していて、全国津々浦々を自転車で旅するような活動をしてました。加えて学生の頃のアルバイトは自転車店…と自転車の魅力に取り憑かれて、人生が変わった人間の一人です。サイクリング好きが高じてメーカーに就職をしたわけですが、どこへ行っても自転車に関して変わらない事実がありました。それはサイクリングは事故リスクの高いアクティビティということです。
サイクリングをするようなスポーツ自転車はスピードも出ますし、二輪の特性上、転倒のリスクがどうしてもあります。ソースが見つかりませんでしたが、サイクリングはケガのリスクが高いアクティビティだという結果が出た調査があったことも記憶しています。 私自身は大きな事故の経験はありませんが、周囲では救急搬送を伴う事故がたびたび起こっていたり、同じくサイクリング好きが高じて入社したはずの部下が自転車事故の後遺症が原因で退職するのを見送ったこともありました。
また、2022年、2023年とロードレース中の死亡事故もありました。特に2023年のツールド北海道の死亡事故は大々的に報道されましたから記憶に残っている方もおられるかもしれません。
私自身、サイクリングと出会ってなければ今の人生はありません。サイクリングのおかげで今があると言えます。そんな大事なサイクリングが不幸な人を生み出す現状を変えられないか、とプログラミング学習をしながら考えるようになりました。
とはいえ、現職で取り組もうとするとROIありきとなってこのような活動はなかなかできません。そこでフィヨルドブートキャンプの卒業制作に、このテーマを組み込んで「サイクリングで不幸になる人をゼロにする」取り組みの小さな一歩を始めました。
その結果できあがったのが「MAAKS」です。
コミュニティを超えて知見を共有する
MAAKSの根底にあるのはWEBエンジニアの「知見を共有」する精神です。
サイクリングには統一した情報網がありません。一方でWEBエンジニアにはQiitaであったりZennであったり様々なプラットフォームが存在しています。
サイクリングルートにおける知見(難易度や危険スポットの情報)についても、車体を購入した自転車店であったり、サイクリングコミュニティ、友人知人、はたまたライドイベント主催者など数多くのローカルコミュニティが独自に情報を持ち、ローカルコミュニティ内でのみ展開されてきました。
つまり、サイクリスト界隈にはコミュニティが独自に持つ知見がコミュニティを超えて共有される環境がありませんでした。
そこでMAAKSは事故などのリスクが有る地点に限定して共有するプラットフォームとして情報共有を通じてサイクリング時の事故リスク低減を目指しています。
使い方
危険スポットをみる
マップ上に表示された水色のマーカーはその地点でなんらかのトラブルに見舞われた人の投稿があったことを示します。 水色のマーカーをタップ or クリックすることで、投稿された内容を確認できます。
アカウントを作る
このサービスではサービス内で独自のアカウントを取得するか、Googleアカウントを使用することでログインすることができます。 ログインすると危険スポットを登録することができるようになります。
危険スポットを登録する
マップ上をタップ or クリックすると赤いマーカーが表示されます。 赤いマーカーを長押し or 右クリックして「スポットを作成する」を選択すると、サイドメニューに危険スポットの登録メニューが表示されます。 必須事項を記入し、「登録する」を選択するとデータが保存され、スポットが登録されます。
危険スポットを削除する
自身が登録した危険スポットは削除することができます。
アカウントを削除する
「マイページ」→「プロフィール編集」でアカウントの削除を選択することができます。 アカウントを削除するとこれまで作成してきたスポットも削除されます。
技術スタック
- バックエンド
- Ruby 3.2.2
- Ruby on Rails 7.0.5
- フロントエンド
- 認証
- devise 4.9
- omniauth-google-oauth2
- リンター/フォーマッター
- Rubocop 1.53.1
- ESLint 8.33.0
- Prettier 2.8.4
- テスティングフレームワーク
- minitest
- RDBMS
- インフラ
- CI/CD
- GitHub Actions
- 外部ストレージ
- AmazonS3
- 外部サービス
開発
ペーパープロトタイプ
制作するサービスのコンセプトが決まった後にペーパープロトタイプを作成しました。
マップ上に地点を表示するサービスはすでに先行して大量に存在します。ユーザーの利便性を考えて先行するそれらのサービスにある程度似せた画面構成と操作性を採用することで、私のサービスを使用する際においてもユーザーが操作方法を覚える負荷を低減させることを目指しました。ここでターゲットマークにしたのが「Google map」と「大島てる」の2つです。この2つのサービスはかなり使い込んで画面構成を検討しました。
この段階で技術的な骨子の部分は「マップを表示して、ユーザーが登録した地点を表示する」とかなりシンプルになるだろうと想定していました。
技術選定
技術選定におけるテーマとして以下の3点がありました。
- 時間短縮を目指すこと
- フロントエンド/バックエンドはRailsが提供する一貫した環境を利用すること
- 外部サービスと連携すること
1つ目は時間短縮です。現職で平日日中に働きながらの制作+プライベートでは結婚式や家探しなどを同時並行していたので専念できる方に比べて時間がありませんでした。
そのためチャレンジングな技術スタックはあえて控えめとし「形にすること」を第一優先しました。とはいえ、Rails 7とHotWireもカリキュラム外ではありましたので、このあたりは0から学びました。
2つ目はフロントエンド/バックエンドにRailsが提供する環境を利用することです。
Railsをバックエンドに使用することはFJORDBOOTCAMPのカリキュラムで学んだことを活かしたかったので既定路線でした。
一方でフロントエンドはカリキュラムで学んだVueと使うか、トレンドのReactを使うか、はたまた直近のFJORDBOOTCAMP卒業生が多く採用していたHotwireを採用するか悩みました。 最終的にHotwireの「バックエンドで処理をおこないHTMLを返すことで動的なクライアント画面を実現する」アプローチはフロントエンドの責任が重くなりすぎた現状を変えることが目的、というRails開発者DHHのメッセージを読み、とても共感と好感に思ったことからHotWireをフロントエンドに選択しました。
3つ目は外部サービスとの連携です。サービスコンセプトが先にありましたのでマップをどうするか、という課題から自然と外部サービスを活用する方向性ではありました。とはいえ、普段自分が使っているサービスでもGoogleアカウントでのログインのような外部サービス連携はよくあるのでユーザーの利便性向上のため、実用に足るサービスにするためチャレンジすることにしました。
開発中に感じたこと
わからないことが多い
自分一人でサービスをローンチする経験はもちろんのことなかったので、サービス開発はわからないことだらけでした。
暗中模索というのがぴったりで、ふわふわとした完成形だけが頭の中にイメージとしてあるけれどそれを実現する方法はわからない。常に手探りの開発でした。
現職で全く未知の業務に数ヶ月単位で取り組んで完成させた経験があったのでストレス的な部分はそれほどでもありませんでした。未知の分野へのストレス耐性に関してはフィヨルドブートキャンプでの学習のおかげもあったかもしれません。
とはいえ未知なことが多すぎて処理の妥当性に判断がつかないことが振り返ってみてよくありました。調査し尽くしたはずだけれども、確証がないので結論が出せない状態になってしまう感じです。そういったときはスクール内で質問をするなどして立てた仮説を結論にしていきました。
カンバンをこまめに管理した
わからない部分が多いので可能な限りカンバンにタスクを書き出して管理していきました。 カンバンはGitHubProjectを使い、簡易的なスクラム形式で取り組んでいました。GitHubProjectを使ったスクラムはフィヨルドブートキャンプのチーム開発プラクティスで培ったノウハウでした。自作サービスでも週一の進捗報告会があってリズムを作れるようにスクール側で用意してくれているのは助かりました。
カンバンを細かく管理することで細かいながらも進捗が視覚化され、メンタルの安定にも繋がりました。また、分からない部分が多い話とも関連して、自分がなぜこのタスクに取り組んでいるのかがわからなくならないようにしてくれるのもメリットでした。
サービスの持続性を高めるために低コストに
フィヨルドブートキャンプ内でも案内がありますが、サービスに固定費が発生する場合は比較的短命に終わり、クロージングしてしまうという話があったので、MAAKSでは可能な範囲で低コストを心がけました。
例えば地図に関わるAPIに関しては王道といえるのは「Google Maps」でしたが、MAAKSでは後発の「MapBox」を採用しました。コスト面ではGoogle Mapsが28500回の呼び出しまで無料に対し、MapBoxは50000回まで無料だったので圧倒的にMapBoxのほうがコスト負担が軽く、サービスの寿命を伸ばしてくれるだろうと判断しました。
こういった技術選定をできることも自作サービス開発の醍醐味だと思います。コストを取るか、ドキュメントや周辺情報などの充実度を取るか、ビジネスにおいても悩ましいポイントじゃないでしょうか。経験できてよかったです。
UI/UXはローカルアプリケーションを参考に画面遷移を極力なくした
UI/UXでこだわったと明確に言えるのはモーダルを多用した部分です。スクール受講生が送り出すサービスの多くが「サービスの説明/紹介ページ」→「サービスページ」という順番で作られています。これが非常に煩わしく感じていました。
というのも私自身の生活においてWEBサービスをそこまで使っていません。多くがスマホで操作するローカルアプリでした。そしてビジネスにおいても多くの事業会社がそれぞれアプリを送り出している時代です。操作性のスタンダードはローカルアプリにあり、と考えてローカルアプリを参考にした画面づくりに取り組みました。
そこで形になったのがモーダルを使用してサービスページに説明や紹介のコンテンツを埋め込む方法です。これによってサービスのメイン部分を使用する限りにおいては画面遷移なく使い続けられることができます。
PC用の挙動とスマホ用の挙動を作って最適化した
スマホ、タブレット、PCでの使用を考慮してレスポンシブデザインを導入しています。
開発中の苦労したポイント
マップ⇔サービスでデータをやり取りする
「マップを表示して、ユーザーが登録した地点を表示する」機能を実装するために、MapBoxのAPIを叩いて出力される座標データを取得して、MAAKS側でユーザーが入力した事項とセットでDBに保存、必要に応じて保存したデータをMAP上に出力できるようにする必要がありました。
この実装をするには
- MapBoxのAPIから必要なデータを取得すること
- フロントエンド(HotWire Stimulus)でMapBoxAPIから取得したデータ、サービス側から取得したデータを扱う処理を構築すること
- Railsでフロントから渡されたデータを適宜処理すること、反対にフロントに適宜データを渡すこと
と、これまで学んできたこと+このサービスで開発する上で学んだことを組み合わせることで構築しました。応用編といった感じです。
例えばMAAKSでは、マップ上をクリックすると現在地を表す赤いマーカーを表示します。その赤いマーカーを操作することで危険スポットの登録画面を表示することができます。
// MAP上に赤いマーカーを表示するメソッド createNewSpotMarker() { this.mapTarget.mapbox.on("click", (e) => { if (this.mapTarget.newMarker) { this.mapTarget.newMarker.remove(); } this.mapTarget.newMarker = new mapboxgl.Marker({ draggable: false, color: "#cf5d40", }); const el = this.mapTarget.newMarker.getElement(); el.id = "new_spot_marker"; el.setAttribute("data-controller", "spot new-spot-marker"); el.setAttribute("data-spot-target", "spot"); el.setAttribute("data-action", "contextmenu->spot#showSpotMenu"); this.mapTarget.newMarker.setLngLat([e.lngLat.lng, e.lngLat.lat]).addTo(this.mapTarget.mapbox); }); }
this.mapTarget.mapbox.on("click", (e) => {})
はMapBoxのAPIです。クリックした地点の情報を取得します。
そこからStimulus(JavaScript)を用いてMAP上にマーカーを出力。マーカーに対するイベントや座標情報をデータ属性として付与させることでその後の危険スポット登録画面の呼び出しや、データをDBに保存する場面にデータを受け渡しています。
MapBoxAPIを安全に利用する
MapBoxのマップを安全に利用しようとするところには盛大につまづきました。
MapBoxのマップはMapBox上のアカウントに紐づいたアクセストークンを読み込ませることで地図を画面上に表示できるようになります。
マップはCDNのようにフロント側で外部から読み込んで表示されており、アクセストークンをフロント側に渡す必要があります。このときに「アクセストークンはフロントに渡していい情報なのか、サービス利用者から見える形になって問題ないのか」という部分の判断がつかず、結論さえ分かれば非常に簡単な話ではありますが苦戦しました。
判断するためには一般的な話として外部APIを使う上で使われる手法を確認する必要と、MapBoxではどのようにしているか、を確認する必要がありました。ドキュメントやMapBoxのDiscordコミュニティを調べた結果、MapBoxのサービス側で読み込み元のURLを制限できることがわかりました。このやり方で妥当かどうかをフィヨルドブートキャンプの質問タイムで確認してようやく判断がつきました。
現在ではサービス側でURLを制限した上でcredentials
として環境変数のようなかたちでアクセストークンなどは読み込むようにしています。
外部ストレージサービスを導入する
MAAKSではユーザーごとに画像をアップロードしてアイコンを設定することができます。
当初はFly.ioのVM(=バーチャルマシン)のローカル(volumes)にファイルを保存する形を検討しておりましたが、AmazonS3を導入することに切り替えました。
Fly.ioのVMは2台体制で稼働しており、保存されるファイルはそのどちらかに格納されます。また、ファイルを読み込む際には2台のVMのどちらか1台のみを読み込むため、ファイルが存在しない方のVMを読むこむとファイルは表示されません。
Fly.ioのドキュメントや掲示板を読み込み対応を検討した末、フィヨルドブートキャンプで先行してFly.ioを使用していた方を参考にしつつ、Railsが推奨する「本番では外部ストレージサービスを使う」ことに従い、AmazonS3を導入てファイルを保存する方法を採りました。
「本番では外部ストレージサービスを使う」ことはRails Guideにはさらっと書かれていたのみ&本番の運用経験がなかったことから予想外の対応になりました。
開発してよかった部分
躓いたところは記録に残す
カンバンの管理と同様の話になりますが苦戦した部分はNotionやフィヨルドブートキャンプ内の日報などに記録をこまめに残しておきました。この記事もそれらを片手に「こんなことがあったな」と思い出しながら書いています。
苦戦した箇所リストは今後ブログのネタ帳として再利用される予定です。
開発に取り組むことで技術にさらに興味が湧いた
開発に取り組んできたことでRails(特にHotWire)にさらに興味が湧いて勉強会(HotWire.love)にも参加しました。
仕事としてプログラミングに取り組んだ際に、私はどのように技術と向き合うんだろうと思うことがありましたが自然と好奇心が湧いてコミュニティに参加するアクションを取れました。エンジニアとして生きる上では学習が欠かせないことは既知の事実でしたので、将来に対する不安が若干減ったように思います。
調べる先が広がり、海外の媒体に対するハードルが下がった
開発に取り組んでいて不明点が出てきたときの対応の幅が広がりました。
自作サービス開発以前は日本語媒体やコミュニティ、英語圏であってもせいぜい公式ドキュメントに調べる先が限定されていましたが、自作サービスを作る過程の中でどうしてもこれらでは情報を得られないことがたびたび発生したのでそういったときには英語圏の媒体、stackoverflowや公式のコミュニティ、時には中国語圏の媒体まで調べる先を広げました。
翻訳機能を使ってなんとかしてる状態ではありますが、調べることに関しては海外の媒体を活用することのハードルは下がりました。
これから
機能を追加したい
一旦完成は迎えましたが、やりたいことはまだまだあります。
- 危険スポットの画像を投稿できるようにしたい →事故時のケガの画像が投稿されるんじゃないかと思って保留中
- マップ上のスポットをクラスター表示させたい →投稿の多いエリアはトラブルをもたらす何かがあると推測できる
- サイクリングのルート生成機能 →MapBoxのAPIとしてあるようです。リリース後の集客次第…。
- コミュニティ機能 →サイクリングコミュニティを形成する拠点としてこのサービスを成長させたい
などなど。
まずは多くの人に使われるサービスになることを目指して、パフォーマンスやUI/UX、利便性の向上に取り組みたいと思います。
いずれはサイクリストの誰もが使ってくれるサービスになることを目指します!
おわりに
自分の思い描いたサービスが自分の手で形になって開発環境で動いたとき、デプロイして世の中とサービスが繋がったとき、本当に嬉しくなりました。大変なことも、苦しいこともありましたが、自分がスキルアップし前に進む感触を得られる日々は楽しいものでした。
サービスの構想を練り始めたころから考えると、リリースまで半年ぐらいかかってしまいました。それでも諦めず、粘り強くこのサービスを完成させたいと気持ちが続いたことは多くの方に支えていただいたからです。
今後もこの経験を糧にして、スキルアップしていきたいと思います。
最後までお読みいただき、ありがとうございました!
謝辞
企画・開発・レビューまで面倒を見ていただいたmachidaさん、komagataさん、フィヨルドブートキャンプのメンターの皆さん、 切磋琢磨したフィヨルドブートキャンプの受講生の皆さん、 このサービスの案出しに付き合ってくれた友人のS、
そして、この取り組みを理解し支えてくれた家族に感謝します。
ありがとうございました。