〇〇のセールを見逃さないサービス作りました。【AV半額オブザーバー】

はじめに

みなさんは、AVのセールを見逃して後悔した事はありませんか?

私にはあります。と言うことでAVの割引を見逃さない為のサービスを作りました。具体的に言うと、割引になってる作品を人気順の一覧で見れるページと、会員登録から『指定の作品が割引になったとき』のみ通知を送るページを通して、より便利な購入体験を提供します。


無題.png

https://hao.japaneast.cloudapp.azure.com (18歳未満はアクセス禁止です)



出来ること

①割引作品を人気順に表示

一覧.png

これが一応メインコンテンツですね。紳士淑女のみなさんはFanzaから定期的に似たようなメールが届くかもしれませんが、メールをいちいち確認するのって面倒じゃないですか?

しかもキャンペーンページは一時的な物なのでブックマーク出来ません。なので割引作品の一覧を毎日更新し、人気順に表示するリストを作成しました。両サイドにはリストの前後40件の作品画像を背景として使用しています。一覧性という面ではかなり拘った仕様なので、使っていただけると幸いです。




②女優別で割引作品をリスト化

女優.png

例によって黒塗りの多い画面ですが、女優さんの名前を登録すると出演作品が割引になった場合自動でリストに表示する事ができます。お気に入りの女優さんの作品がセールになってないか確認したい。そういう時にぜひご活用ください。




③マイリストを設定
リスト.png
URLを直接入力することで監視リストに動画を追加することができます。Fanzaにも似たような機能はあるのですが、ポチポチって押してるとすぐリストの件数が膨れ上がりがちなので、「これぞ!」と思うお気に入り動画の記録にオススメです。




開発の舞台裏


「個人開発だし、どうせフロントはJSで書くからNode.js使おう」

技術選定に関しては多分ここから始まったと思います。PHPという選択肢もあったのですが、今更感が強かったので今回は不採用。逆にフロントは詳しくなかったので資料が充実しているVue.jsを選びました。インフラはKeyVaultなる素敵機能を使ってみたかったのでAzureです。

image.png

(大正義KeyVault君、実際これが無ければ普通にAWS選んでた気がする)





大まかな開発の流れとしては
①設計
②バッチの実装
③バックエンドの実装(ルーティングとか主要なAPI)
④フロント(htmlやVue.js)の実装
⑤バックエンドの修正(フロントを実装した事でバグや考慮漏れが多数発覚)
⑥デザイン
⑦インフラ周りの設定とか
⑧リリース

という感じだったような気がします。一応番号は振りましたが④⑤⑥に関しては「プロトタイプが欲しい」「今日はデザインの気分じゃない」「あれ実装してないじゃん」みたいな理由で結構反復横跳びしてますね。


まあ時系列順に振り返ると、設計に関しては結構グダりました。
というのも、今までオープン系のアプリ開発しかやったことがないし、ましてや設計なんて初めてなので、「DBから作ろうぜ!」って発想になる訳です。

まあこれも依存の方向から考えると一定の合理性はあるのですが、そうなると問題なのが「アレこうした方が良くね(仕様変更)(仕様漏れ)」からの「DB設計直さなきゃ駄目じゃん……」からの「既存設計に引きずられて苦しい………(死)」となる黄金パターン。今回はどっぷり嵌りました。

ねー、WebバックエンドのDB設計なんて機能が増えれば当然カラム増えるのになんで最初にやったんだろね。
今から思えば裏側はモックに留めてフロントから仕様をガチッと固めてしまうのが最速な気がしますが、その反省は次回に活かすとします。



そしてある程度設計が固まってから実装に取り掛かったのが5月の頭ですね。
この段階ではフロントエンド全然わからんマンだったのと、バックエンドもいきなりWebサーバ書くのは脆弱性作りそうだったので消去法でバッチから作り始めました。(その結果、後で半分以上書き直しになった)

特に大変だった所は無いと思いますが、JSをガッツリ書くのは何気に初めてだったので、バッチ処理全体を書くのに丸々1か月も掛けてしまいました。Promiseが全然分からなくて死んでたり、コードのどこが非同期処理で動いてるのか今一掴めずに「forが動かねぇ……」って絶望してたのも今となっては懐かしい思い出です。


6月は丸々ルーティングとHTML、ログイン認証の実装に使いました。
後述しますが、特にログイン機能の実装は結構大変でした。まず認証が通らなかったり、逆にどんな値でも通ったり、通ってもセッションを保持できなかったりとてんやわんやです。SNS認証とか実装しなくて本当に良かった

7月はようやく機能とデザインの実装です。
CRUDとかバックエンドの側の実装は6月から並行でちょいちょい着手してたのですが、いざ動かしてみたら変な所で止まったりバグッたりと散々だったので、実質書き直しになりましたね。

デザインに関してはTailwindCSSを使って見たのですが、今思えば教養として素のCSSを書いたほうが良かったかもしれません。TailwindCSS、作る時は便利なんですがHTMLが汚くなるし、sm:invisibleするとSafariで表示崩れるし、運用フェーズだと都度都度ビルド要るしでなかなか面倒。



8月前半は細かいデバッグや表示の追加をメインに行いました。レスポンシブの対応とかもそうですが実際使ってみると思ったより使い勝手微妙だなみたいな感じになったので位置を修正したり、ツールチップを実装したりしました。表示に関してはあまりゴテゴテするのも良くないかもとか思ったけど、余白を生かしたデザインとかアレはアレで高度芸能(素人は手を出さない方が無難)なので、今回は出来るだけ詰め込んでみました。次回作はもっとゴリゴリに表示を動かしたいを思います。

8月後半は予定を繰り上げてAzure上にVMを立ち上げ、テスト環境を構築。
まあ私のTwitter見てる方は既にご存知かもしれませんが、詰まりに詰まり散らかしました。うん、何というか3ヶ月半掛けて作った動くはずのアプリが突然動かなくなったら焦るよね。

まあ原因の9割はmysqlのせいだったんですけど、テーブル名にキャメルケースを使ってはいけない。大事な事なのでもう一回言うけど、テーブル名にキャメルケースを使ってはいけない。Do you understand?
データベースオブジェクトの命名規約

9月はほぼ消化試合でしたね。本番移行って言っても丸々同じ手順を繰り返すだけですし、まあ強いて言えば、ここに来てダダ下がりしたモチベの維持が大変でした。リアルでも色々忙しかったのもあり、ここの工程はボリューム的には正味一週間ぐらいで終わりそうな感じだったのですが、何やかんやで丸1か月掛かりました。


ちなみにAzureのVMやKeyVaultで幾らか詰まると思ったのですが、その辺りは意外にも公式ドキュメントだけですんなり構築できました。流石マイクロソフト謹製のPaaSですね。ネタに出来なくて残念です




出来なかった事

・TypeScript
・Nuxt.jsとかのナウいフレームワーク
・綺麗なコード
・自動テストとか

image.png

まあ今回の1件でJavaScriptとSQLさえ書ければ全てが解決するという事を証明してしまったので、学習意欲はわりかし低めです。下2つは時間があったら極めたいなと思いつつ「時間があったら」などと逃げ口上かましてる時点でやる気無いんだよなぁ   みつお


それにTypeScriptは記法の癖が強いし、Nuxt.jsも何というか色々勝手にやってくれる代わりに覚えること多いしで、個人的にあんまり好きじゃないです。あときれいなコードは目指したかったんだけど、それが開発速度を担保してくれるかって言うとそうでもないので、動いて読めてそこそこ整理されてればいいかって感じに妥協しました。



一つ反省点を上げるとすれば、自動テストを導入するかどうかをさておいても、テスタブルな実装は目指すべきだったなと思います。

具体的な所で言うと、APサーバの実装で内部の処理とレスポンスの返却を同じメソッドに書いてしまった結果、単体でテスト出来ないコードが生まれたりって感じですね。インターフェースいちいち考えるの面倒くさかったとは言え、最後のテストが地味に大変だったので、ここはもうちょっと拘っても良かった気がします。






頑張った点とか

①自動ログイン

これは今日日どんなサイトでも付いてる機能なのでどうにか実装したかったのですが、結構手間取りました。というのもnode-modulesにあるpassport君は自動ログインをサポートしてないので、認証の発火とCookieへの読み書き、ハッシュ化したトークンの生成や管理は自前で実装する必要があります。(あれ?これもう自分でミドルウェア実装したほうが早くない?)

まあ最終的にランダムかつ一意なハッシュを定期的にセットし、passport経由で認証を通すことに成功しました。まあメッセージ返す部分は結局解決できなくて、ログイン認証の前に「ログイン認証のメッセージ」を返すAPIを叩くことで解決しました。悔しい……



②女優検索

DBはmysqlなのですが、流石にLike検索で返すのは遅い&不便なので、ngramによるフルテキストキーを貼ってあいまい検索を実装しています。細かい調整とかは出来なかったのですが、まあまあ実用範囲かなと、本当はサジェストとかやりたかったんですが、それは次回の宿題ですね。



③ツールチップ
これはログイン先にあるマイページでの演出なのですが、URLや縮小画像にマウスカーソルを持っていくと近傍にオリジナルのパッケージ写真を表示するようになっています。

実装としてはシンプルにJSでイベントを発火してdiv要素の内容とCSSを都度都度DOMで書き換える感じです。ただ何故かz-indexが適用されず、悩みに悩んで一日詰まった結果、div要素の位置を5回ぐらい変更してようやく適応される位置を見つけることが出来ました。


④reCAPTCHA
このサービス、実はそこそこセキュリティにも力を入れていて、ログイン画面にはDDoSにも対応したBOTの検出機能を実装したりしています。ただ登録画面に関しては人とBOTを区別するのは難しいのでreCAPTCHAを使用しました。

セキュリティ上バックにも実装が必要って言うのは分かりますが、フロント側で一度作り込んだJSを書き換えるのは結構ヒヤヒヤしました。登録時に実際のURLが必要ですが、実装するなら最初からテンプレートを組み込んでコメントアウトしておいた方が良いです。



ちなみにバックの実装はこんな感じの関数を書いて適宜叩けばOKなので意外と楽でしたね。

reCAPTCHA
function reCaptcha(token){
    if(!token){
        console.log("Error token is null");
        return false;
    }
    let options = {
        url: 'https://www.google.com/recaptcha/api/siteverify',
        method: 'POST',
        form: {
          secret:/*シークレット*/,
          response:token
        },
        json: true
      }
      request(options, (error, response, body) => {
        if (error) {
          console.log(error);
          return false;
        }
        // 閾値により判定する
        if (body.score < 0.5) {
            return false;
        }else{
            return true;
        }
    });
}



⑤Slackの活用
通知の送信用として使用しているG Suiteには2000通/日の送信制限があります。まあ制限にかかることは無いと思いますが、容量を無駄に削るのも嫌なので、自分宛ての通知(お問い合わせとか鯖の情報とか)はバックエンドからSlackAPIを経由して送信するようにしています。

これが中々便利で、メールと違ってスマホにプッシュ通知が出るので通知を見逃すという事もありません。これから個人開発を始めようという方はぜひ使ってみて下さい。




詰まった所とか

①Promise

最初はメソッドチェーンとか面倒臭いなって感想しか出てこなかったんですが、短い関数でちゃっちゃっとPromise返したら、async/awaitで普通に同期実行できると知ってすぐに手のひらクルーしました。

便利ですねこれ。………………え?まだコールバック関数とか使ってる人居るんですか?


Promise自体の説明に関してはやめ太郎さんの書かれた記事が日本一分かりやすいので是非どうぞ
4歳娘「パパ、Promiseやasync/awaitって何?」〜Promise編〜




②passport

前述の通りログイン関係でPassportを使っているのですが、ログインした後セッションを有効化出来ないという現象が発生しました。具体的にはユーザー情報が入ってるはずのreq.userが何故かundefinedのまま……………


まあ結論から言えば犯人はAxios君でしたね。

axios.defaults.withCredentials = true;

を適当な所に書いてあげれば、認証情報を含むCookieをサーバに送信できるようになります。




③Cheerio

スクレイピングと言えばCheerio君。ただコイツには結構苦労させられました。結論から言えば近傍5つ以内にClassが振ってない要素を取得するには新卒Sierを秒で退職してMicrosoftに就職を成功させるぐらいの豪運を必要とします。実質砂金掘りですね。

必要な要素が取れないときは闇雲に試行回数を増やすよりも、別のページから同じ情報が取れないか探したり、PCの電源ケーブルを抜き差ししたり、懐かしのゲームを楽しむのが肝要です。実際私も自棄になり、開発をほったらかして一日中エスコン6をプレイしていたら何故か要素が取れない問題が解決しました。

あとeachで要素の取得をぶん回すとき、attr("href")は先に find("a")を指定しないとちゃんと拾ってくれないので気を付けましょう。




④Vue.js

まあこれに関しては後で別の記事に起こそうと思いますが、特に苦労した部分だけでも紹介したいと思います。


・v-modelの変更を監視してイベントを発火したい。

まあ誰しも一度はやると思いますが、<input v-model="hoge" @change="huga()">みたいな感じのやつです。
ちなみに↑のコードはv-model@changeでイベントリスナーが二重になってるので@changeは動きません。
一応公式ではv-modelをv-bindに書き換える方法が推奨ですが、v-bindだと何故かバインド出来ない現象が発生したので別の対策を講じます。(←今思えば普通にタイポだった可能性ある)

対策、new Vue()の時にmodelのリスナーを追加する

let vue = new Vue({
  el:'#app',
  data:{},
  watch:{/*監視したいv-modelの名前*/:function(){

    //変更があった時に走らせたい処理を記述

  }}
});



・new Vue()の中からMethodsのメソッドを呼びたい。

当然ですがthisが必要です。この例に限らず、Vueでundefinedって怒られた時はまずthisの付け忘れを疑いましょう。





⑤Linux

これLinux(Ubuntu)君は全然悪くないけど、環境移行する時に結構詰まったので覚え書きを残しておきます。

まずアプリを雑にインストールするとWindowsとバージョンが違うという事を肝に命じておくべきだと思う。Node.jsとか結構顕著で、何と12.18.3 LTSだとtry{hoge()}catch{/*何もしない*/}みたいな変な文法のコードを書いても普通に動いたりする。動くなや

あと一度やった変更はメモったほうが良いですね。ミスした時もリカバリが効きやすいのと、環境立て直す時「何かCronの動きがおかしい」→「時刻設定がグリニッジ標準時でした」とかやりがちなので対策したほうがベターです。




⑥mysql

リピートアフターミー、テーブル名にキャメルケースを使ってはいけない。

何故かって言うと雑にmysqlをインストールした時、WindowsとLinuxでバージョンが違うから。8.0(win)だとCREATE TABLEの時に名前を全部小文字にしやがるしてくれるけど、5.7(Lin)だと普通に通る。なので突然テーブルが見つかりませんとか言われて死にます。死にました。

あとLinux版は権限周りが固いっぽいので、雑にパスワードを設定するとERROR 1819 (HY000): Your password does not satisfy the current policy requirementsで怒られます。

良い子のみんなはRSA2048に記号挟んだパスワードをぶち込んでいこうな!




まとめ



作り終わった今だから思うのですが、半分以上無駄な機能を作ってしまった気がします。

作った当初はDMMにお気に入り機能があると知らなかったのでガッツリ作り込む気満々でしたが、ただ単に「半額〇〇の一覧をブックマークできるサイト」というコンセプトならログイン機能とか付けずにトップページだけ作って2~3ヶ月でリリースするのが最適解でした。

まあそのおかげでCRUDの実装とかも出来ましたし、Linux触ったり、SQLをガッツリ書く経験も積めたので結果オーライだと思います。これで私も真にフルスタックエンジニアの仲間入りですね。



まあそんな与太は置いておくとしても、やはり個人開発に関して大切なのは『モチベの維持』と『まあいいかの精神』だと思います。

というのも納期が無い開発で仕様なんて固まってる訳ないですし、設計はグダグダ、人手は足りてないといった具合に個人開発はおおよそ炎上or凍結プロジェクトの前提条件を満たした状態でスタートします。今回モチベの維持としては主にGitHubを利用しました。定期的にPushしないと芝が枯れるという危機感は進捗にかなり貢献してくれますね。後はまあ今のバイト先が普通にブラックなのでそこから脱出したいという思いも地味にあったりします。

『まあいいかの精神』と言うのは、一定以上のクオリティを求めない事ですね。今回実はパフォーマンスに関しては結構ザルでして、VMのスペックに関しては同接100~200ぐらいしか見込んでないですし、アプリ自体もDBにインデックス張ったぐらいで計算量の最適化はやってなかったりと中々お粗末な仕上がりです。

ただ、同人界隈でも「出ない神本より出る糞本」という言葉がありますし、「それでも要件は満たすから良いだろ」ぐらいの割り切りが肝要だと自分は思います。それに今回アプリを作ることで、間違いなく自分の実力は伸びました。実を言うと9月にモチベが枯れかけた理由も半分以上それで、急に実力が上がったせいか「今更これを公開するのか?」みたいな躊躇いと気恥ずかしさを乗り越える必要がありました。ここだけの秘密ですよ。


あとここまで辿り着けた要因の一つとして、Twitterで進捗を呟く度に「いいね」を下さるファボ魔(約2名)の存在も大きかったと思います。多分アレが無ければ8月ぐらいで折れていた疑惑ある。大変感謝です。


それとこれは余談なのですが、次回作については既に設計が上がっています。詳しいコンセプトはまだ秘密ですが、今度こそきっと皆様の度肝を抜くサービスをお届け出来ると思います。それではまた次回、goodbye

ji_ji
Javaを愛し、Javaに愛された空前絶後で超絶怒涛のプログラマ(見習い) お願いだから叩かないで………… GitHub https://github.com/jiji0222
https://twitter.com/jiji_0222
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした