こんにちは。エイチームライフデザイン技術開発室の@aiji42です。
先日、弊社が運営しているイーデスというメディアの関連サービスとして、「イーデス専門家相談Q&A」という新しいサービスをリリースしました。
暮らしとお金に関する悩みや質問をサイトに投稿すると、住宅ローンや資産運用など専門分野ごとに実績があるファイナンシャルプランナー(専門家)や、同じような悩みを持っている一般のユーザーからアドバイスや意見を無料でもらうことができるというサービスです。
今回はその新サービスの開発・運営で採用した技術スタックや、それを採用した理由を紹介します。
技術スタック
アーキテクチャの概略図は次の通りです。
まず、フロントエンド及びバックエンドはRemixで構築しており、クライアントへはサーバーサイドレンダリング(SSR)でコンテンツを提供しています。
アプリケーションサーバーとしてGoogle CloudのCloud Runを採用しており、リクエスト量に応じて柔軟にスケールできるようにしています。
アプリケーションサーバーの前段にはCDNとしてCloudflareを配置しており、将来的にWorkersとKVやD1などを使って柔軟にコンテンツをキャッシュしたり、フィーチャーフラグによるコンテンツのコントロールをしたいと考えています。
また、Remixが生成したクライアント用のスクリプトや画像などのアセットファイルの配信にはCloudflareのR2を採用しました。
データベース(PostgreSQL)と認証基盤にはSupabaseを採用しています。
商業利用であれば最低25ドル/月というお手頃な価格で利用でき、またサーバーレス環境からのコネクション対策としてPgBouncerがデフォルトで提供されているという点が採用の決め手です。
そして、データベースのマイグレーション管理とORMにPrismaを採用しました。
アーキテクチャには現れない、開発用の技術スタックとしては次のようなものがあります。
まずリポジトリは、一般ユーザー向けのアプリケーション(user)、社内管理用のアプリケーション(admin)、DBのスキーマや認証等の共通ライブラリ(base)、の3つに分割したかったので、モノレポ(yarn workspace)を採用しました。
そのモノレポ管理とリモートキャッシュによるジョブの高速化のためにTuborepoを導入しています。
そして、自動テストにはVitestとPlaywrightを採用しています。
採用した技術の理由や背景
技術選定の情報を理解するために、サービスの機能や要件が必要になってきますので、主要なものを簡単に箇条書きであげておきます。
- 質問者は質問の投稿・編集・削除・回答への返信・ベストアンサーの決定が可能
- 質問者以外は質問に対して回答が可能
- 全ユーザーが質問や回答に対してのリアクション(いいね)が可能
- マイページにて自身のプロフィール情報を編集できる
- 上記操作のためにはサインアップ・サインインが必要
なぜRemixを選択したのか
弊社では、自然検索からの流入をメインとするメディアサービスを多く運営しており、クローラビリティやCWV(コアウェブバイタル)を考慮した上で、コンテンツ配信時にSSR(サーバーサイドレンダリング)やSSG(サーバーサイドジェネレーション)を行っているサービスが多くあります。
それらのサービスの開発にはNext.jsが採用されることが多く、この専門家相談のサービスも同様にSEOファーストな要件が求められていたため、技術選定の際にはNext.jsが候補に上がりましたが、最終的にはRemixを採用しました。
Next.jsではミューテーションリクエストはAPI Routesで受けるか、もしくはGraphQLサーバーなどを外部に用意することが一般的です。これは、ナビゲーションとミューテーションの知識が分離される事を意味します。
本サービスの開発チームでは、バックエンドとフロントエンドの分業はなく、エンジニアが両方を同時に開発するため、この分離はあまり好ましくありません。ページ単位の知識(処理)をそれぞれのルートファイルに凝縮した方が管理しやすいのです。
そして、Next.jsではミューテーションのレスポンスを受けた後の制御は、クライアント側がハンドリングしますが、そうではなく、クライアント側の挙動をサーバー側で制御できると、クライアント側のコードを薄くシンプルに保つことができます。
この課題を解決してくれるのがRemixでした。
Remixは、GETリクエストを受けるためのloaderモジュールと、GET以外(POSTやDELETE)のリクエストを受けるactionモジュールとを、それぞれのルートファイルに持つ構成を取ります。
他にもプラットフォームやベンダーの依存が少ない点や、Cookieを簡単かつ安全に取り扱えるように設計されているので、認証・認可処理をすべてサーバーサイドで完結させてクライアント側のコード量を少なく保てる点も、今回のサービスの要件とマッチしました。
あくまで、ユーザー投稿型のサービスの開発(かつ今回の開発チーム)においてはRemixの方が相性が良かっただけであり、すべての状況下でRemixがNext.jsより優れているということではありません。
GETリクエストが大半なメディアサイトでは、SSR・SSG・ISRなどのレンダリング戦略を選択できるという点でNext.jsの方が間違いなく適しています。
RemixにはSSRしかないため、コンテンツデリバリの高速化・最適化のためには、レスポンスヘッダーとCDNでのキャッシュコントロールの知識が必要です。
そして、フロントエンドとバックエンドが分業しているような組織においては、先に述べたナビゲーションとミューテーションの知識は、むしろ分離されている方が分業が進むはずです。
GraphQLは採用しなかった
Next.jsが採用される可能性があったプロジェクト初期は、ミューテーションにGraphQLを用いる予定でした。
先に述べたようにNext.jsのAPI Routesのみでミューテーションを受けるのは現実的ではなかったのと、またPersisted Queryを採用しCDNでクエリをキャシュすることでの高速化を視野に入れていたためです。
GraphQLを採用しているプロジェクトは社内に多くあるため、GraphQLの採用は既定路線となっていました。
一方で、初期の開発チームのメンバーはバックエンドもフロントエンドも両方同時に開発できるがゆえに、GraphQLサーバーを挟むことでワンテンポ開発スピードが遅れてしまうという課題もありました。
最終的にはRemixを採用したことで、loaderとactionからORマッパーであるPrismaを通じて直接データベースにリクエストできるようになりました。
そして、Remixが提供するFormコンポネントやhooksにより、従来のHTMLネイティブなフォームサブミットの記述でデータ送信が可能です。実際には内部で非同期のデータ送信に置き換えられ、サブミット後はページドキュメントをフルにロードしない差分フェッチで、再検証やナビゲーション(リダイレクト)挙動が自動的にハンドリングされます。
これによってGraphQL(ないしREST API)を用意する必要はなくなり、シンプルな構成でアプリケーションの開発が可能になりました。
Cloud Runのようなサーバーレスアーキテクチャで構築する場合、データベースとのコネクションのオーバヘッドや接続数が課題になりますが、Supabaseがデフォルトで提供するPgBouncerがその課題を吸収してくれるのも、この意思決定を後押ししてくれました。
技術選定のための活動
今回の新サービスの開発ではRemixを採用しましたが、決して唐突にRemixを採用したわけではありません。
私が所属している技術開発室では、1年以上前からRemixに注目し、社内でのPoC活動を通じて有用性や実サービスの開発に耐えうるかどうかを検証していくと同時に、OSS活動を通じて開発コミュニティへの参画や、周辺ライブラリのメンテナンスなどを行ってきました。
例えば、社内の技術研鑽イベントで使用するダッシュボードをRemixとSupabaseを組み合わせて作ってみたり、社外のイベントでRemixに関しての情報発信をしたりしたりなどもその一環です。
我々を取り巻くWeb業界(特にフロントエンド)は急速に変化しており、日々新しい技術が生まれては廃れてを繰り返しています。
このように複雑化していくシステムや開発スタイルを、シンプルにそして最適化するために、我々は技術に対しての投資活動を行いながら日々の業務を行っています。