ストレイテナーマニア向けにやってほしい曲の投票サイトを作った話

Page content

ふとした思いつきと個人的な好奇心および開発欲でやってみたくなったので、作った。ここです。
https://straightener-mania-2024-vote.ricemountainer.net/

投票受付中なので、ストレイテナーを知ってる人はぜひとも投票してほしいッ!!

ここではその開発経緯や開発のポイントなどを振り返る。どっちかというと技術的要素が大きいのでストレイテナーの話はほぼしないです。テナー勢だけどITエンジニアじゃない人は無理して読まなくてもいいです。(後半の「運営者として」は、技術的観点はないので、暇つぶしに読んでもいいかも)

はじめに

きっかけは以下のツイートである。

投票サイトって言ったって、要するに、曲のリストつくって、select optionかなんかで選ばせて、選んだのをサーバーにPOSTし、あとは適当にホゲホゲしてDBに放り込めばいいだけだ。非常にシンプルな入力フォームが1枚だけで、究極的にはそれだけで完結する。確認画面と完了画面いれても多くて3画面。全然難しくない。ちょっとガチれば2~3日で作れるんじゃね?と思って、ノリと勢いで開発に着手した。実際にはもうちょっと時間かかってしまったんだがw、まあでもそんなに時間はかかっていない。以下、開発中のお悩みや細かい経緯など。

開発者として

  • 少し悩んだのは、連続投票防止の仕組みをどうするか?という点だ。今まで利用者視点で、ストレイテナーやナッシングスの楽曲投票を経験してきたが、方式としては

    1. 「会員サイト限定」あるいは「Googleアカウント」みたいに、投票する側のユーザーが事前に一意に特定できているか
    2. 投票する場所が不特定多数に開かれていて(つまり会員かどうかに関係なくだれでも投票出来て)、なんらかの形で1日1回に限定しているか

    という、大きく分けてこの2種類だった。で、理想は1.だった。例えばAuth0でGoogleやSNSをOauthの対象に加えれば比較的簡単に実装できることはわかる。Auth0相手の認証の仕組みなら実績もある。(今運用中である)しかし、

    • これをやっちゃうと、投票の手前で少なからず一度「認証」のステップが入ってしまうし、人によってはなんらかのユーザー登録を要する場合もあるので、「投票」が目的なのにその手前でなんで面倒くさいことしなきゃならねんだ、みたいに若干腰が重くなると思った。個人的にはもうちょっと気軽に「投票」にきてほしかった。
    • そもそも元からそんなに凝ったモン作るつもりはなかった。テナマニ2は2024年8月で、開発しようと思いついたのが2024年5月上旬、想定される利用期間は実質2か月ちょっとである。そんな短命前提のシステムに気使うつもりはなかったし、したくもなかった。理想は「思いついたが吉日」からのスモールスタート。マーク・ザッカーバーグの言葉を借りれば「完璧を目指すよりまず終わらせろ」である。
    • ついでにいうと、投票した人をいちいち特定できる状況を作りたくなかった。いや正確に言うと興味がなかった(ので、作りたくなかった)。あくまで「投票結果」というmassな状況が必要なのであって、個々人が何に投票したとかにはあまり興味がない。し、別にそれほど見たいとも思わない。それに、投票する側も「そういう(何に投票したかを俺に知られる)状況になる」ことで投票を渋る可能性もあると思った。そんなことするつもりはないんだが(できたところでどう悪用するんだって感じだが)、結局投票する側から見れば俺なんてただの得体のしれないオッサンだからな。単なる個人サービスだからこそ、この辺の線引きはドライにしておく必要がある。

    というわけで2.の方式と相成った。ちなみに判断基準はシンプルにIPアドレスである。このため、なので例えば家庭用のWiFiとスマホのキャリア回線を切り替えたりとかでやろうと思えば1人で1日複数回投票可能になるが、そこまでアクセスが殺到するサイトだとも思ってないし、そもそも2か月しか運用しないんだし、まあなんとかなるんじゃねと思ってこれで良しとした。シンプルなほうがわかりやすくていい。

  • 実装方式はNext.js。Hostingはどこに乗せてもまぁ良かったんだが一旦Vercelにした。無料がおいしい。バックエンドはSupabase。投票結果を後々集計することを考え、RDBにすることは開発前からの前提必須条件であった。なのでORMとしてPrismaも採用。筋トレサイトの開発で学んだが、この開発体験はとても良い。それとDNSにCloudflare。ただしプロキシはしない。プロキシしちゃうと、アプリ側で取得できるIPがCloudflareのIPになっちゃうからだ(上述したように、連続投票防止制限にIPアドレスを用いるので、クライアントのIPをさらっと取りたかった)いやまぁやろうと思えばプロキシしててもHTTPヘッダとか拾ってクライアントのIPとれるんだが、面倒くさいのでプロキシしない。あとキャッシュの世話したくないし。という感じ。静的ファイルがたくさんあるわけじゃないし、必要な分は全部/public配下にいれこんだ。なのでアセット用のS3やらR2やらもない、シンプルな構成。なお、一部のAPIはVercelのEdgeにキャッシュしてもらうようにした。これ、なんだかんだいって実は初めて使ったんだが(キャッシュする場合はCloudflareに頼ることが多かった)、手軽でいいなと思った。どうせCloudflare使うならキャッシュもそっちに任せればいいじゃんって発想はあるものの、VercelにHostingするなら選択肢としてはアリな気もしてきた。機会があれば今後活用事例を作りたい。

  • react-selectを使ったリッチUIの選択リスト、確認画面用のreact-modalFormikyupのvalidation、APIでのPOST、ORMのPrisma、などの諸々は、自作漫画サイトRESIGN THREAT自分用筋トレアプリで既に作成済なので、その辺で作ったComponentやUtilityなどをそのまま移植して再利用している。まぁライブラリのバージョンアップ等があったので若干手直しが必要な部分は出てきたが、大した話ではない。なので今回の件で、この辺のコマい部分はほとんど自分では実装していない。過去の自分が既に対応済。ありがとう昔の俺。そういう意味では、サイト本来のビジネスニーズを満たすための開発作業にほぼほぼ集中できた(し、その目論見が開発着手前からあって、かつほとんどその目論見通りだった)というのは大きい。

  • Next.jsはちょうどいい機会だからApp Router使ってみようかな~?と思ったんだが、SSG作ろうと思って面倒くさくなってあきらめた。(後述)目的はApp Routerの勉強じゃなくてサイトの作成なのであって、ここに時間かけるのは違うと思った。で、Pages Routerに切り替えたらあっさり作れた。やっぱりPages Routerが慣れててこっちの方がわかりやすいなあ。個人的には…

    • 「App RouterでSSG作ろうと思ったけど面倒くさくなってやめた」の部分についての詳細。Pages Routerでいうところの、getStaticProps使ってサーバー上の静的ファイル(JSONファイルとか)を読み込んでビルド時にページ生成するタイプのページ、いわゆるSSG(Static Site Generation)、を、App Routerでどうやって書くのかわからない。いや正確に言うと、シンプルなページならできるんだが、ページ側で"use client"を必須とするようなライブラリを使う場合に、サーバー上の静的ファイルを読み込むことができない。もっと具体的にいうと、react-selectを使う場合、"use client"が必須になるんだが(これ付けないとビルド時に"v.createContext is not a function"とかいうよくわからんエラーが出る。vの部分はビルドのたびにxとかaとか色々変わる)、"use client"をつけていると、ページ描画用のデータを読み込むためにfs.readFile使うことができない(fsモジュールがクライアントサイドでは使えないため)。というわけで個人的にここで詰んでる。こちらの記事の「fetchが使えない場合」の解説が、状況的には多少近いんだが、ページのレンダリングがシンプルに<div>とかだけで構成されているので、ページ側を"use client"にしなくて済んでおり、結果fs.readFileとかも使えているので、俺のケースとは異なっている。"use client"のページでfs.readFileを使ってビルド用のデータを読み込むにはどうすればいいのだ?? って話。そもそも"use client"=クライアントサイドのコンポーネント、っていうのはSSGかSSRかを決定づける要素なのかどうか?すら、まずわかっていない。現状の理解では、おそらくこれは多分違っていて("use client"はビルドの挙動を決定づけるものではない。と思う。)、"use client"はあくまでReact Componentそれ自体の挙動を指示しているものであって、Next.jsのビルド方式を指示していない。というより、App Routerにおいては原則としてすべてSSGとしてビルドされるようになっているらしいから、"use client"はそれとは無関係なのだ。多分。要するにページの挙動を指示しているだけの"use client"がNext.jsのビルドの挙動にまで余計に影響してしまっている?ように見える。こんな程度のことはPages Routerでは楽勝で実装できていたものなので、正直App Routerになってから使いづらくなったなぁという感想が大きい。慣れてない(理解が浅い)せいだと思われ、蓋を開けてみると「な~んだ、そんなことでよかったの」って感じになるんだと思うが、Pages Routerのバックグラウンドが強すぎるせいで、どうにも馴染めない。どういうところから切り崩していこうか…悩み中。
  • 投票結果の確認をどうやって行うか?がちょっと課題だった。最初はあまり深く考えずに「まぁDB入ってselect投げればいいか」くらいにしか考えてなかったし、実際それで事足りるっちゃ事足りるのだが、毎回psqlでDB入ってSQL投げるってのもちょっと面倒くさいし、例えば外出しちゃうと簡単にはそういうこともできなくなる。ので、なんらかの形で「俺だけが見える」ようなPublicな入り口を持つデータ抽出処理を作る必要があった。

    • 「俺だけが見える」って点でいうと、自分用の筋トレアプリは、CloudflareのWAF使って我が家のグローバルIP以外からアクセスできなくしてるので、それに倣うかと思ったが、このサイトは上述の通りCloudflareにプロキシしてないので、その方式は使えない。いや正確に言うと、それを実現するためには別のアプリ作ってどっかに乗っけたうえで、Cloudflareにプロキシさせればできなくはない。でも、それは面倒くさい。もうちょっと手軽にできないか?と思い、投票の防止機構に使っているIPアドレスの取得ロジックを使って、アプリケーション側で弾くことにした。これはアプリケーション(Vercel Function)のリソースを消費してしまうので、できればやりたくなかった(やるならその手前のWAFで止められるのが理想ではあった)のだが、まぁでも使うの俺だけだし生きてても2か月程度だしな…とおもったので別にOKとした。それに加えて隠しパラメータであるトークン値をもらい、これが特定の値と一致いていない場合はエラーとするようなロジックを組んだ。入り口をUI上物理的に用意しない時点でまず大丈夫だろうし、万が一わかってもIPとトークン値でほぼ確実に防げる。もうこれでいいだろ、、、ということにした。
    • 作ってみて思ったが、これはおそらく興味深いテーマだ、と思った。要するにエンドユーザーに使わせるフロント側と、それの裏でメンテナンスを行うための管理側の分離の話なのだ。真面目にやろうと思うと、エンタープライズ的な思考に基づけば、もう少し大掛かりな(凝った)仕組みが必要になる。さすがにこんな小規模短命サイトにはそこまでのパワーを割くつもりはなかったし、まぁそもそも仮に割こうと思っても俺にはそんな予算もないので実行不可能だったわけだが、ただ「自分の手が届く範囲で実現するならどうするか?」というのは、考え出すとなかなか面白い課題だと思った。シンプルにいくなら、DBを共有する2つの別のWebアプリを用意して、片方はPublicに、もう片方はCloudflareプロキシしてWAFで弾いて、って感じになるのだろう。閉域網みたいなガチなやつは俺には無理なので、WAFで弾くこと前提で管理側も外に露出せざるを得ない。あとは認証基盤を手前に挟んで、って感じか。自分の手が届く範囲で実現可能な構成だとざっくりとはそんな感じになりそうだが、これはもう少し深堀してみたい話である。こういうの考えるのは面白い。
  • この関連で、投票する側の人でも部分的に現在順位がわかるような機能をつくった。

    すべての順位が、得票数含めてリアルタイムで完全にわかるような機能は作れるし、というかむしろそっちの方がシンプルでわかりやすい・作りやすいのだが、これを投票する側の人に開放するのは違うと思っていた。投票という仕組みである以上、「投票の終わり」まで順位は明かさないようにしておく必要があるだろう。なので、現在順位を公開するにしても部分的にしなければならない。どこかのタイミングで結果集計して中間発表みたいにしてもいいのだが、そういう手動のスナップショット的な行動が若干面倒くさいというか性に合わず、どっちかというと「仕組み」(機能)にしたかった。で、「投票結果のサマリからランダムに1曲選んで順位表示、ただし上位数曲はランダムに選ばれる範囲から除外」という機能に相成った。これは、どちらかというと発端は 「作ってみたかった」 というかなり自分本位な理由で、投票の本旨ではないし、オプションである。わかりやすく言えばITエンジニアとしての好奇心に負けたのだ。作ってみた後の今振り返ると、若干微妙な位置づけの機能になっちゃったなという感は確かにある。「中間発表用のスナップショット」にしても、その時点では手動作業のイメージしかわかなかったが、例えば上に書いたようなオレ専用の機能にして、パラメータに日付とか指定できるようにしておけば機能化はできたわけで、こっちのほうが良かった気はしないでもない。(つまり、終始、エンドユーザーには順位を完全に明かさないというスタイル)まぁでも、これ実は裏でVercelのキャッシュを利用しているのだが(Response Header見ればわかるw)、そういうプラットフォームの機能を利用する機会が作れたのもあって、開発者視点ではつくって良かったと思っている。

  • そもそもの話として、あまり調べてないんだが、こういう感じのアプリケーションって、わざわざ自作などせずとも、探せばそれっぽいSaaSかあるいはテンプレートみたいのありそうだなという気は、しないでもない。実際、この前テナーはGoogle Formで投票させてたしな。確かにGoogle Formでも投票や投票結果の集計はできる。だが、今回の場合は、仮にそういうのがあったとしても、俺はおそらく自作していただろう。なぜなら 「作りたかった」 からだ。今回のこれは、こういうITエンジニアとしての興味と好奇心が原動力になっているので、「出来合いのもので済ませる」という発想自体が頭になかった。仮に今回のを出来合いのソリューションで進めていたら、なんかちょっとパっとしなかったこと間違いないだろう。(少なくとも俺はパっとしなかったと思う)要するに「自作すること」に意味があったのだ。「なんでこんなセンスのねぇ糞UIでわざわざカスみたいな投票サイト作ってんだボケ!出来合いでもっちいいのがあるんだからそっち使えタコ!」みたいなのを感じる人もいるかもしれないが、このように最初の発想がそもそも違うという点はご理解いただきたいと思う。効率性とか運用性とかそういうのはある意味度外視しており、基本的に「オタクムーブメント」の生成物なのだ。

運営者として

  • これ書いてるのは5/21の夜なので、投票開始から約10日経過した時点である。この時点での投票結果の1位は、記憶にある限り3~4日目の時点で既に1位だった。途中2位に抜かれたりもしたが、一応現時点でも1位をキープしている。この状況は、見ていて非常に面白くて、なにがこの曲をここまで「推してる」んだろう?というのが、純粋に気になる。あくまで投票の「結果」しか見れないので、そこに至った思考はわからないのだが、少なくとも複数人からこの曲に票が入っているのは確実で、なぜこのタイミングでこの曲なのか?というのは、単純に興味がある。これは俺の個人サイトだから(公式でのアクティビティではないから)、演奏が確実に保証されているものではないが、逆にそれが幸いして当人の純粋な「曲への思い」が現れた結果なのだろうと予想しており、そういう人(同じ曲に対して同じ思いを持った人)がこんなに集まったのが、単純に不思議。この曲なんかありましたっけ???ちなみに、あくまで個人的な意見ですけど、現在の1位はそこまで「マニア」な曲っていう印象でもありません。
  • こういう、「順位の変遷」というか、「得票の状況」は、見ていてとても面白いなと思った。今までテナーやナッシングスで投票を「開催していた側(運営者側)」は、こういう思いで投票結果を眺めてたのかと思うと、なんといい立場なのだと思うと同時に、自分が実際にその立場になったことで、その面白さを実感している。まぁ公式の運営側が同レベルのマニアであるかどうかはわからないが、マニアな運営者としてデータを見ているのはとても面白いです、まじで。「あ~わかる~~~」とか「お~これね~~~」とか、感想が無限に出てくる。それを独り占めできるっていうんだから面白くないわけがない。みんなやるといいと思うよ。ニヤニヤが止まらない。w
  • 「テナモバ会員かどうか?」は、別に投票に必須の要素ではないという認識はあるものの、集計の切り分けのためのフラグ情報の一つとして個人的に入れたかった。というのも、この前のナッシングスの武道館投票が、一般向けに広く実施されたこともあってか、王道曲に票が集中した節が見受けられたので、「逆に会員限定にしたら結構順位変わったんじゃないかな」と思ったからである。まぁ自己申告制なので参考程度にしかならんのだが、ここはできれば切り分けて集計してみたかった。ただ、投票開始から10日ほど経過した今、これの有無で投票結果に顕著に差が出るような状況は見えてきていない(残念ながらそれが顕在化するほどの得票数がおそらくない)。もう少し票が集まらないと傾向がわからない可能性はあるので、しばらく様子見ようと思う。
  • このサイトは、冒頭書いた通りで、もともと完全に俺の趣味として始めたものだ。みんながどんな曲聴きたいかを知りたいという、ただの個人的な好奇心が発端になっている。ただ、順位を見てると「ほほぉ~~~この曲か~~~」って感心することがあり(まぁそれが目的だったのだが)、また入力いただいたコメントにみなさんのストレイテナーというバンドの曲に対する熱い思いが溢れていることが分かった。…という背景があって、この投票結果を俺で閉じてしまうことに、なにか強い違和感を感じている。 このデータを集めた(集める能力をもった)人間として、この結果をちゃんと然るべき場所に伝える責任があるのではないか? と、重い言い方するとそう思うように、今なっている(別に思い悩んでいるわけではないが)。もっと具体的に言えば、これをストレイテナーの運営側に共有したい。別にこれの中から1曲やってくれとかそういうことが言いたいわけではない(やってほしいけど)、ただ純粋に 「ストレイテナーというバンドとその曲を愛している人たちがこんなにいますよ」 というのを伝えたいだけだ。データを見てるとそういう思いにあふれており、俺の手に余るというか、貰った側としてこれを然るべき場所に共有する責任がありそうな気がしてきた。ただ、仮にやるにしても、どうやって・いつ伝えたらいいものか?など、まるでわかっていないし、決めてない。当然公式にはそんな窓口があるわけでもないし、そもそも伝えることの是非もまだ決まっていない。こういうのは思い立ったが吉日というか、時が経つと面倒くさくなってやらなくなるので、ぱっぱと動いた方がいいのは経験上分かっているので、やるなら近日中になんらかの動きをとりたい。どう考えても、これは俺のオ●ニーで消費するには惜しいデータな気がしてならないのだ。

まだもう少しの間運用していくシステムではあるので、また最後に振り返ろうとは思うが、少なくとも現時点では、開発してよかったとは思う。純粋に技術者として、開発体験を楽しめているというのもあるし、IT系の趣味とは別方面の趣味(音楽)を組み合わせて、楽しむこともできている。衝動的にスタートしたプロジェクトなので、アプリケーションのつくりや運用など、細部はやはりイマイチな部分もあるが、まぁこういうのも含めて経験だからな。スモールスタートのちょっとした個人開発って意味では全然問題なく楽しめてる範囲だと思う。そういうわけで、引き続き投票お待ちしております。