こんにちは!ふーがです。
この度、書籍のセール通知サービス「せるれぽ」をリリースしました🎉🎉🎉
このサービスについてや苦労したところ、そこから学んだことなどを書きたいと思います。
あなたはだれ?
非エンジニアからWEBエンジニアを目指して学習中のふーがと申します。フィヨルドブートキャンプで学んだ知識と技術を活かしてこのサービスをリリースしました。
どんなサービス?
一言でいうと「書籍のセール通知サービス」です。
サイト上で書籍を検索して「通知を受け取る」ボタンを押すと、その書籍がセールになったときにメールで通知します。
どんな人が使うの?
以下のニーズを想定しています。
- 今すぐ読みたいわけじゃないけど、いつか読みたい書籍がある
- 書籍はできるだけ安く買いたい
- 定価だと高いけど安くなったら買いたい
- 安いサイトをいちいち探すのが面倒
何か当てはまる部分があればぜひ使ってみてください!
なぜ作ったのか
プログラミングを勉強していると、いろいろな技術書が欲しくなりますよね。
でも全部が全部今すぐ読みたいわけじゃないし、全部買ってたらお金がいくらあっても足りない。。。
なので僕は、Amazonの「欲しいものリスト」に”いつか読みたい本”をいくつも登録して、毎日価格をチェックしては「今日も値段変わらんな。。。」「お、今日この本安くなってる!」とかやってました。 さらにはAmazon以外のDMM Booksや楽天Booksでも、同じ本の価格を調べては比較して…というのを繰り返していました。
しかし、正直これはダルイ。
毎日何冊も何サイトもチェックするのはもう疲れた。
。。
そうか、じゃあ自分で作ればいいんだ。
作れば毎日のAmazon巡回業から解放されるぞ!!という苦労話(?)から生まれたサービスです。
技術スタック
- Ruby on Rails 6.1.4
- Ruby 3.0.2
- Vue 3
- AWS - EC2, RDS, Certificate Maneger, SES, WorkMail
なぜRailsとVue.jsなのか
フィヨルドブートキャンプで学習したのが主にRailsとVue.jsだったからです。
学んだことの総復習という目的があったので、わざわざ他の言語で…という選択肢はありませんでした。
とはいえ、実際にサービスを作るにあたって必要となる知識は学んだことだけで全てをカバーできたわけではありません。実現したいことに対してRailsではどう実装するのが良いのか?その方法は?などなど、調べたり学んだりすることが山ほどありました。
結果的に、復習どころかそれ以上の学びを得られて成長できたと感じています。
なぜAWSなのか
理由としては3点です。
- 技術的な挑戦要素を入れたいという想いがあった。
- インフラ環境をワンストップで構築したかった。
- 実務で触る前に触っておきたかった。
さきほど学んだことの総復習が目的と話しましたが、フィヨルドブートキャンプにはデプロイのカリキュラムは今のところありません(「発展編」という卒業の要件ではないカリキュラムにはありますが)。
なのでデプロイ先をどうするか、というのは完全に個人の裁量になっています。
そこで、せっかくだし何かしら技術的な挑戦要素を入れたかった、というのが1点。
また、このサービスの特性上メール配信の環境も構築する必要がありました。
SSLにも対応したかったし、独自ドメインのメールサーバも欲しい。
AWSならこれらも含めてワンストップで構築できる点が魅力的だった、というのも理由の1つです。
そして1番大きい理由としては、AWSは実務的にも使われているケースが多いようなので、実務に入ってから初めて触るよりも今のうちから触っておきたい、という点です。
使い方
新規登録&ログイン
リンクから登録またはログインします。メールアドレスとパスワードで登録するか、Googleログインが利用可能です。
初回登録時はメールアドレスあてに認証メールが届くので、メール内のリンクをクリックしてもらうと登録完了になります(メールが届かないなどあればぜひ教えてください!)。
書籍検索
ログインすると検索フォームが出るのでここから書籍を検索します。ワード検索か、13桁のISBNで検索できます。
たとえば「ruby」というワードで検索すると
こんな感じでワードと関連する書籍の検索結果が出てきます。セール通知を受け取りたい書籍の「セール通知を受け取る」ボタンを押すと、通知リストに登録され、以降セールの時にメール通知が届きます。
通知設定
「どれくらい安くなったら通知するか」は設定できるようになっています。ヘッダーのアカウント設定アイコンをクリックして
「通知する割引率」のところでお好みの割引率を選択して、「更新」すれば設定完了です。
なお、デフォルトでは「1円でも安ければ通知する」になっています。これだと、人によっては「通知が多くてうざい」かもしれないので、お好みで設定できるようにしました。
メールを待つ
あとはセールになってメール通知がくるのを待つだけです。気長に待ってください。
ちなみに、セールになるとこんな感じのメールが届きます。
リストから削除する
購入が済んだらもう通知は必要なくなると思うので、リストから削除します。ヘッダーのリストアイコンをクリックして
「削除する」を押せばOKです。
開発について
開発期間
エレベーターピッチが固まってからレビューが終わるまで、4ヶ月弱です。
もう少し早くできるかな、と見積もっていましたが甘かったです。
技術検証が甘かったせいでうまく見積もれていなかったと思うので、ここは大きな反省点です。
開発の進め方
最初の段階で、GitHubのProjectsに実装したいことややるべきことをガーッとissue登録しまくって、あとはひたすらissueをさばいていくスタイルで進めました。
途中で思いつくことがあれば随時issue登録していました。
これをやるとやるべきことはもちろん、進捗状況や「こんなにやったのかー!」という達成度もすべて可視化されてよかったです。
また、淡々と作業を進めていけたのもこの進め方のおかげかなと思います。
工夫・苦労したところ
1つのコントローラーで複数のモデルを操作していいのか?
「セール通知を受け取る」ボタンを押したときに、DBに書籍やリストが登録済みかどうかなどの複数のモデルにまたがる処理を書いてしまったため、コントローラーが肥大化してしまっていました。いわゆる「Fat Controller」というやつですね。
# 当時のコントローラーの実装 # createだけでこんなにあった… class Users::ListsController < ApplicationController def create item = params[:list][:book] @isbn = item['isbn_13'] @price = item[:price] @title = item[:title] @author = item[:author] @image = item[:image] @url = item[:url] @sales_date = item[:sales_date] if current_user.list.blank? @book_list = List.new(list_params) @book_list.save else @book_list = current_user.list end # この辺から本来のコントローラーの責務を超えてしまっている if Book.find_by('isbn_13' => @isbn).nil? @item = Book.new(book_params) @item.save else @item = Book.find_by('isbn_13' => @isbn) end unless ListDetail.find_by(list_id: @book_list.id, book_id: @item.id).nil? redirect_to request.referer, alert: 'すでに追加済みです。' return end list_detail = ListDetail.new(list_detail_params) if list_detail.save redirect_to request.referer, notice: 'リストに追加しました!' else redirect_to request.referer, alert: 'リストに追加できませんでした。' end end . . end
これの解決方法は質問などもして「Form Objectを使う」とか、「別のクラスに切り出して呼び出す」などいくつか候補を考えました。
結果的には、検索結果一覧のページをVue.js化したタイミングで、VueからそれぞれのモデルのAPIコントローラーを呼び出す形にして解決しました。これによって1つ1つのコントローラーは薄く、かつ複数のモデルを扱わずに済むようになりました。
あまりこの辺の設計を考えずに実装を進めてしまったことが原因かなと思います。
コードを書き出す前に、コントローラーの処理やモデルの扱いを含めた設計まで考慮しておかないといけないなと考えるきっかけになりました。
どこまでVue.js化するか
フロントにVue.jsを使いたい、というのは前述のとおりなんですが、「どこまでVue.jsにするのか?」というのが難しかったです。
「とりあえずフロントは全部Vue.js!!」という風にはしたくなくて、Vue.jsを利用することで恩恵を享受できるところをVue.js化していく方針で考えたんですが、これが難しい。
悩みまくっても答えが出なかったので、どういった線引きでVue.js化するのが良いか、メンターさんに相談してみたところ、
- このサービスはページ構成がシンプルなのでVue.js化するメリットを感じづらい。
- 線引きとしては「ページ遷移しないことがメリットになるかどうか」。
というアドバイスをいただき、なるほどなーと思いました。
もっとメニューが多くあったり操作が複雑なサービスだったら、SPAのようにページ遷移しないことがメリットになるのかもしれませんが、僕のサービスの場合はUIがシンプルなので、Vue.js化するメリットがそこまでないんだなと気づけました。
なので、特にこのメリットを感じられそうな、画面遷移なくCRUD処理をさせたいページだけVue.js化しました。
「フロントをVue.jsで書く」のは、学習したからにはお決まりでそうする的な感覚を持ってしまっていましたが、そうではなく、ちゃんと明確な理由をもって実装する大切さを学びました。
いくら安くなったら通知するか
セールの通知サービスなので、「セール」という概念は大切です。
個人的には1円でも安いなら通知して欲しい派なのですが、「数百円くらいじゃセールじゃないよね」という声もあり、僕の感覚だけで「セール」の概念を決めてしまうと、使いづらいサービスになってしまいそう、と思いました。
じゃあいくらからにしよう?
20%以上とか?でも10%安い通知が来ないのは個人的には不便だな。
「これは一概には決められない。」
というわけで、「セール」の概念は人それぞれ違うでしょうし、通知設定をユーザごとに切り替えられるようにしました。
これで、必要な通知が必要なときに届く、「セール情報」になったと思います。
この考えにいたるきっかけをくださったメンターのkomagataさんに感謝です🙏
クローラーがメモリを食いまくって落ちる
書籍の価格情報は定期的に各販売サイトをクローリングして取得しています。
クローラーのアクセスが販売サイトの負荷にならないように、1サイトへのアクセスが終わったら別のサイトを見て…という感じにして、負荷が1サイトに集中しないようにしていました。
にもかかわらず、クローリング実行中のプロセスを見てみるとchromeが無限増殖しているではありませんか…!!
ローカルで実行しているときは大丈夫だったのですが、本番環境で実行するとメモリ使用量が増えすぎて途中でクローリングが強制終了されてしまっていました。
ここで対策として、
- EC2(本番環境)のスペックを上げる
- クローラーのロジックを見直す
のどちらかかなと考えたのですが、どちらが正解かわからなかったですし、そもそもメモリ使用量が原因かどうかも自信が持てませんでした。
1人で考えても答えは出ないと思ったので、この状況と自分で立てた仮説をもとにメンターさんに相談しました。
メンターさんが実行時のプロセスの状況やクローラーのコードなどを見ながら一緒に考えてくださり、どうやら「役割を終えたプロセスが生き続けている」のが原因のようだとわかりました。
なるほど、ということは、役割を終えたプロセスは明示的にプロセスを殺してあげれば大丈夫そうだな、とわかりさっそく修正。
# 実際のコードとはちょっと違うけど例 Capybara::Session.new(:selenium).tap do |session| session.visit url session.instance_eval(&block) session.quit # これを追加した end
狙いどおり、無限Chrome状態が解消され、本番環境でもクローラーが最後まで動いてくれるようになりました。
これまでメモリなどの物理的なリソースを意識してコードを書いたことがなかったので、このデバッグで
- 物理的なリソースも考慮したコードを書く
- 問題のボトルネックを的確に把握してコードに反映させる
のが大切だということを理解できました。
AWSなんもわからん
これはやる前からわかりきっていたことではありますが、実際にAWSを触ってみて「マジでなんもわからん…」となりました。
正直、構築のほとんどはネット上の記事を参考にしてやった感じです。
ただ、サーバ関係は環境に依存したエラーが多く、ネット上の記事をそのまま写経しても思ったとおりに動くことは少ないです。ほぼ100%何かしらのエラーが出ます。
そういう時には必ず「ログを見る」ことを意識していました。
だいたいログを見ると答えが書いてあります。AWSの詳しい仕組みを完全には理解していなくても、エラーログを見て、どこでエラーが起きていて、何をどうすれば解決できそうなのかアタリを付けて調べて実装する、ということを繰り返して構築していきました。
このやり方が正しいのか、また効率的なのかはわかりませんが、なんとか構築しきって本番環境で自分の作ったアプリケーションが動いているのを見たときはめちゃくちゃ嬉しかったです。プログラミングとはまた違いますが、インフラの構築にもおもしろみを感じることができるようになりました。
感想
月並みな感想ですが、最初から最後までとにかく楽しかったです。
どういうサービスにしようかと考えるのはもちろん、実現したい機能に対して「どういう風に実装しよう?」と考えるのも、ボタン1つ配置するにも「どこにどういう大きさで配置するのがいいだろう?」と考えるのも、全部がわくわくして楽しかったです。
もちろん、うまくいかなくて数日悩んだこともありましたし、行き詰まって暗鬱とした気持ちになることもありましたが、それ以上に開発を通して感じた"楽しさ"は何ものにも代えがたいです。
これまでは与えられた課題をどうやって実装するか、というベクトルだったのが、自作サービスは答えがない中で課題を見つけ、その課題をプログラミングという手段で解決していきます。
自分が身につけた知識、技術や調査力を使って、自身が抱えていた現実の問題を解決できたことがこの上なく嬉しいです。
今後やりたいこと
せっかく作ったサービスですからたくさんの人に使ってもらいたいですし、長く使ってもらいたいという想いがあります。そのためにはまだまだ改善したいところ、追加したい機能などなどたくさんあります。
- さらに多くの販売サイトに対応させる
- メールの配信時間を設定できるようにする
- メールの配信頻度を設定できるようにする
あたりは、早めに対応したいと考えています。
もし使っていただけるのなら、使っている中で感じた不便さなどをフィードバックいただけると嬉しいです。
まとめ
さて、結びになりますが、こうして無事にサービスをリリースできたのは当然ながら僕一人の力ではありません。
健やかなるときも病めるときも、温かく見守り的確な指導や助言をくださったフィヨルドブートキャンプのメンター・アドバイザーの皆様、Rubyの楽しさをいろいろな形で感じさせてくれたコミュニティ各位。そして時に励まし合い、時に熱く議論を交わした一緒に学ぶ仲間の皆様のおかげです。
この場を借りて皆様に感謝申し上げるとともに、今後も切磋琢磨しながら成長し合える関係でい続けたいという願望をここに記して締めたいと思います。
最後までお読みいただきありがとうございました!!
あ、リポジトリへのスターや不具合があったときにIssue登録もぜひお願いしますッ…!!!!
Twitterのフォローもぜひにッ!!!