Zenn
💩

技術選定の失敗 2年間を振り返る TypeScript,Hono,Nest.js,React,GraphQL

2024/08/25に公開
9

はじめに

久々に記事を書いたのでどうぞお手柔らかに...
私が過去2年間で行った技術選定の成功と失敗を振り返り、その学びを共有したいと思います。
文才無いので淡々と箇条書きでいきます
Twitterエンジニア垢作りました。エンジニアのお友達がいません。 @uncode_jp

注意

  • 意見を押し付けるものではありません。ただ建設的な議論は大事だと思う。
  • 自分の意見は明確に、歯切れのよい表現を意識している。人それぞれだよねみたいな感じに逃げたくない。技術選定に結論はある(過激)。
  • ただし技術選定にはコンテキストがあり、例えばプロダクトのフェーズや組織の事情によって当然結論は変わる可能性がある。
  • OSSの開発者さん達は偉大ですごい。ありがたく使わせてもらってます。開発者を攻撃する意図はありません。
  • 私はキャリアを通じてあまり多くのエンジニアと接してこなかったので、たまに他社から来た人と話すと色々学びがあったと感じることが多い。間違いの指摘や異なる意見はとてもありがたいです。
  • 私には権威が無いので、○○が言ってたという形で虎の威を借りることがあります。権威主義者には一番響く。

経緯

  • 二年前、シード期のtoB向けSaaSスタートアップに入社した。
  • 当時はVue.js + JavaScriptを1.5年程度やっていて、その前はすこし仕事でLinuxを触っていた程度。
  • 入れ替わりに近い感じで前任者が抜けて、その後数か月間は一人で開発を続けた。
  • 最初あったものはTypeScript, Reactのフロントエンド、Javaのサーバー、AWSコンソールで直接構築されたインフラ(MySQL含む)だった。
  • そこからバックエンドをTypeScriptに書き換え、インフラをTerraformで構築し、DBをPostgreSQLに移行した。
  • 現在は5人のエンジニアチームになった。(パートタイム1人)
  • 未だに技術選定は9割方私が調査して提案している。

現在使っているもの(以前使っていたもの)

  • 言語: TypeScript, C++
  • フロントエンド: React, React Query, Jotai, Tanstack Router, Storybook
  • サーバーサイド: Hono, tRPC, OpenAPI, Prisma, (Java, Express, Nest.js, GraphQL)
  • インフラ: Terraform, AWS, PostgreSQL, (MySQL, AWS CDK)
  • その他: Yarn, Turborepo, Biome, (ESLint)

これらの中で失敗したと思っているものと、その理由を書いていく

Java

結論
TypeScriptでいい。

Javaという言語がすごく悪いとは思ってない。動的型付言語より遥かに良い。
ただモダンな言語と比べると色々不満がある。
Null安全じゃない、地味にVSCodeでの開発が辛い。ビルドがやや遅い。冗長。パフォーマンスも中途半端。
私が慣れていないのを考慮しても、TSのほうが生産性が高いと感じる。

ただ、教養として学習することには一定の価値があるとおもう。
例えば、歴史の教養がある人は歴史的な街並みの観光がとても楽しく感じられる。
それと同様に、Javaの教養があれば歴史の長いシステムに趣を感じられるかもしれない。

言語の選定に関して。使用する言語は少ない方がいいので、組織特有の事情が無ければ99%TSでよいと思っている。
ごく一部のパフォーマンスが重要な部分のみ高速な言語で書けばよい。Ruby on Railsの作者も似たようなこと言ってた。
使用する技術を統一することで生産性も高くなるし、属人化も起きにくくなる。
実際私が仕方なく書いたC++のコードは見事に属人化している。
加えて私がTypeScriptで書いたサーバーサイドのコードの半分ぐらいも属人化している。
なぜかメンバーのキャッチアップが進まない。チーム開発難しい。

MySQL

結論
MySQLを使うと会社が潰れる。

あえてPostgreSQLではなくこちらを選択する理由がない。
一応断っておくと、私はDB自信ニキでもなんでもなく、内部の仕組みはざっくりとしか知らないがそんなことはあまり問題ではない。
アプリケーション開発をする上でMySQLには特に優位性がないのということ。

問題は主に二点、一つ目の問題はRLSがないこと。
MySQLでマルチテナントSaaSをやるとうっかりミスで会社が潰れます。この時点で少なくともSaaS向きではない。
RLSとは、簡単に言うとセッションごとに論理的にDBを分離できる機能。
例えばA社のユーザーからB社のレコードを隠すことができる。
他にも複雑なポリシーを設定可能。

二つ目idが実質連番一択になること。
プライマリキーを連番にしないとパフォーマンスの問題が起きるらしい。連番はURLに使いたくない。
聞いた話だが、ミスによりテーブルAのIDでテーブルBを参照していたという事例があったらしくマジで怖いと思った。会社潰れます。
MySQL+連番は速いというのは知っているが、"実用上"問題になるケースがあるのだろうか。
あとこれは連番自体の問題といっていいのか微妙だが、jsのbigintがマッピングされるのが地味にクソめんどくさい。JSONにするときは文字列に変換する必要がある。
stack overflowか何かでBigint.prototype.toJsonを弄るワークアラウンドを見かけたが、相当強靭なハートを持っていない限りそんなことは恐ろしくてできないだろう。
あと、連番だとIDの生成をDBで行う必要があるため、DDD界隈ではUUID一択らしい。私の会社は零細なのでDDDごっこで遊んでる暇はないので関係ないが。

余談
UberでMySQL+連番をPostgreSQL+UUIDに移行するのに凄まじいリソースを使ったという話を、元Uberのエンジニアから直接聞いた。
連番使用はUber最大の失敗だったと語っていた(ソースは無い)。

Nest.js

結論
フレームワークの学習が趣味の人にはお勧めできる。老後の趣味としてはいいかもしれない。

一体何を解決するためのフレームワークなのか最後までわからなかった。複雑性を増やすだけでは。

半年~1年ぐらい使ってHonoに乗り換えた。
言葉を選ばずに言うと、全身技術負債みたいなフレームワークだと思った。
世界が俺に合わせろみたいなジャイアニズムを感じた。意見が強い。
それが許されるのはReactやRuby on Railsみたいなデファクトスタンダードになれたものだけで、君はそうではない。
一方、有名企業のメルカリとかでも使われているようなのできっと素敵な一面があるのだろう。ぜひ感想を聞いてみたい。

問題点

  • 無駄に分厚い
  • 直観的ではなく学習コストが高い
  • esmoduleの対応予定無し
  • レガシーデコレーター依存
  • ビルド遅い。立ち上がり遅い。パフォーマンスが悪い。(ベースをfastifyにすればいいのでは? => 問題の総数が増えるだけ)
  • monorepoと相性が悪い(なんか独自のmonorepoの仕組みがある)
  • 他のライブラリとの統合にプラグインが必要になる。メンテナンスがされていないものも度々ある。
  • ボイラープレートが多い。コマンドでScaffoldできるがもうそんな時代ではない。

加えて、こう言っては元も子もないが、提供している機能の有用性がわからなかった。
独自のモジュールシステムを提供しているが、あの仕組みが何に貢献しているのかわからない。実行時にエラーが出てストレスがたまる。
多分大体のプロダクトでDIコンテナなんて必要ない。(DIが不要ということではない。)
DIコンテナが無いと困るような複雑な依存関係を作るなということ。
Nest.jsは分厚いので、早めに捨てないと一生捨てられなくなる。

一応擁護すると、Honoが出る以前は、Node.jsのサーバーサイドフレームワークは正直まともな選択肢がなかった。
開発環境のセットアップにすら苦労した。そんな中オールインワンのNest.jsは救世主のように思えた。

GraphQL

結論
多様性の時代には必用かもしれない。

Nest.jsと合わせて使っていた。
思えばGraphQLを使っていた頃、私は通勤中や入浴中にも四六時中GraphQLのことばかり考えていた。
GraphQLのスキーマ設計やベストプラクティス、N+1問題、メンバーへのオンボーディング、complexityの計算方法、その他セキュリティ上の懸念事項等考えることは尽きなかった。
しかし、ふとしたときに正気に戻りこう思った。「私は何と戦っているんだ?」
その瞬間私はGraphQLのコードをすべて削除しtRPCに置き換えた。
結果、日ごろの悩みの半分が無くなりプロダクトの本質と向き合う時間が増え、肌艶もよくなり髪も生え彼女もできた。

問題点

  • スキーマ設計が難しい。学習コストが高い。
  • フロントエンドでもクエリを書く必要があり二度手間。
  • セキュリティ上の問題があり、それを解決するのにそこそこ労力がかかる。
  • ページネーションを実装するのがしんどい
  • 複雑性を増やすだけ
  • 膨大な時間を無駄にする

少なくとも9割の組織では単に手間がかかるだけで恩恵が皆無でしょう。(9割という数字に根拠はない。9.9割かも)
夢から覚めて現実と向き合いましょう。
実際最近GraphQLに否定的な意見もよく目にするようになった気がする。
ただし、多様性の時代なので、Userは取得したいが、User.ageやUser.genderは取得したくないというニーズがあるかもしれない。

tRPC

結論
これである必要がない

最初に言っておくと、そこまで悪いとは思っていない。
ただ、標準から外れたものを使うにはそれ相応の強い理由が必要。
tRPCの良いところは記述量の少なさだが、@hono/zod-openapiでも大差ない。
例えばテスト周りのエコシステムに圧倒的な差があり、実際少し困っている。
あとtRPCのせいでReact Queryのバージョンを上げられないのが地味にストレス。
いずれ捨てるつもりで採用したが、思ったより賞味期限が早かった。

余談
元UberのエンジニアがtRPCにはスケーラビリティに問題があると言っていた。理由は知らない。元Uberは関係無い。

gRPC

結論
不要な最適化

一部のマイクロサービスで使用していたが、Honoに置き換えた。
悪くはないが、Node.jsのエコシステムがやや貧弱。Buf, Connectとかも試したが発展途上だと感じた。
分業が明確でGoを採用しているチームでは良いかもしれない。
JSONよりサイズが小さいがVPC内の通信でそんなものは問題にならない。
あえてOpenAPIではなくこちらを使う理由がない。

Jotai

結論
Propsを書くと腱鞘炎が悪化する人以外にはお勧めできない。

プログラムを学び始めたとき必ず学ぶ基本的なこととして、"グローバル変数を使うな"がある。
しかしなぜかフロントエンドの文脈では皆その教えを忘れ、安易にJotaiやReduxを使ってグローバルな状態を作成してしまい、結果何もかもが結合したおぞましいコードを量産してしまう。
(しかも多くのエンジニアはこれが問題なことを認識すらできない。私もそうだった。)
実際、私はデザインツールのような、一画面に無数の状態があるフロントエンドを設計しているが、グローバルステートの類は一切使っていないし、実際何も困っていない。
単一の画面でこれ以上複雑なフロントエンドは世の中にそう多くないだろう。
データフェッチングはReact Queryを使えばいいし、それ以外の一般的な状態は全てuseStateを使えば済む。
悪い設計を誤魔化せてしまうので、解読、分離不能な巨大な泥団子を簡単に作れてしまう。
パフォーマンス改善目的で使う場合もあるが、そんな最適化が必要になる場面はあまり無い。
せめてContextを使ってツリー内でatomを生成しスコープを狭めよう。それをしない理由はない。
あとステートがクリアされないのも地味に気持ちわるい。

グローバルステートに関する話題だけで1つ記事を書こうと思っているので、これ以上は深入りしないでおく。
一つだけ言っておくとProps Drillingが問題になるのは設計が原因です。

ここで注意なのだが、Jotai自体が悪いわけではない。それを使う側の問題が大いにある。
実際、Jotaiは使い方に関して意見を持たない。
しかし、チームにはいろんな人間がおり、明確な基準を設けないと簡単にモラルハザードが起きてしまう。
だったら最初から禁止しよう。あえて使う必要もない。

余談
ドワンゴ教育事業のチームも状態管理ライブラリを使ってないらしい。2年前の就活のとき聞いた。

Storybook

結論
興味が無い。

これは私が導入したわけではないが、正直未だに価値がわかっていない。
語れるほど使い込んでもいないが、無くて困ったこともあって助かったこともない。
デザイナーとの協業で役に立つかもしれません。あとちゃんとしたデザインシステムがある組織とか。

Yarn (v4)

手が慣れてしまったので使い続けているが、正直v2以降は独自色が強いのでpnpmとか使った方がいいと思う。
多分未だに多くのチームがyarn v2以降の存在に気付かずv1を使い続けている。v1はクソ遅いのでさすがに乗り換えた方がいいと思う。
node_modulesを使わない変なモードがデフォルトになっているが、コミュニティから無視されているので非対応のパッケージが多く実用性はない。
自身が本当に素晴らしいと思うものを作るのは結構だが、エコシステムを尊重しないような独善的な姿勢は受け入れられづらいと思う。

余談
元Uberのエンジニア曰く、オリジナルのyarnの作者が現在はpnpmを使用しているらしい。元Uberは関係ない。

ESLint

結論
v9にアップデートするよりBiomeにするほうが簡単です。

v9で離脱した。今までありがとうございました。
フラットコンフィグとか私からすると正直興味がなく、そんなことに時間をかけられないのでv8のまま放置していた。
プラグインまみれになるのもめんどい。

Emotion

結論
私は満足しているのにVercelがそれを許してくれないようです。

CSS Modulesから当時開発が活発だったEmotionに乗り換えたが、1年もしないうちにServer Componentの関係でオワコンになり始めてる。
Server Componentを使うかどうかは置いといて、明らかに開発が下火になっている。これは負債になる可能性が高い。
もうCSS Modulesでよくないか?どうせZero runtimeとかもそのうちオワコンになるんじゃないの知らんけど。
Zero runtimeに関しては乱立しすぎててAIで生成しづらそう。
一方Emotionの開発体験自体は良い。AIでも生成しやすい。

Kysely

ORMはPrismaがあるが、複雑なSQLを書く必要があり使用してみた。結果後悔している。
とにかく生成AIとの相性が悪い。微妙に間違ったコードばかり生成する。
これなら普通にSQLを書いた方がいい。
地味に学習コストが高い。というかドキュメントが少なすぎて、細かいところで頻繁に躓くのがストレス。
殆どSQLを書いているのにkyselyのキャッチアップコストが追加でかかる。意味がない。

Redis, RabbitMQ

用途によってはPostgreSQLである程度代用できる。使うものを極力増やさない方がいいと思う。

使ってないけど懐疑的なもの 意見求む

  • Next.js: 最適化のためなら我々には必要ない。サーバーアクションとかの開発体験がとても良いなら一考の余地あり。ころころ破壊的変更が入ってキャッチアップが面倒な印象がある。Vercelという会社は人の仕事を増やすのが好きらしい。複雑性に見合うリターンがなさそうなので触れてない。(エアプ並感)
  • MongoDB: RDBでいい。JSONも使える。DynamoDBも使ったことあるけどNoSQLはまじでやめといたほうがいいです。冷静になりましょう。
  • TailwindCSS: このまま突き抜けてスタンダードに上り詰める可能性があるが、そうならずに皆に飽きられたときが悲惨なことになりそう。CSS関係はもう無駄にトレンドを追わずに静観するのが良い気がする。

逆によかったもの

  • React: Vue2, Vue3を使ってたが、結論React以外を選ぶ理由が無い。できることは大差無い。でも世界はReact中心に回っている。
  • Tanstack Router: アップデートでたまにバグを踏むが、開発体験はとてもよい。API設計が素晴らしいと思う。あと半年したら誰にでも勧められる。
  • Hono: モダンなExpressとして使用。シンプルで薄い。学習コスト無し。コード二行足すだけでAWS Lambdaで動くようになる。最高。一応フルスタックフレームワークなのだがフロントエンド関係はあえてHonoでやる理由が見つからない。でも野心的で素敵だと思う。
  • Prisma: とにかく楽。融通利かない時もあるが、最悪SQLを書けばいい。スキーマも見やすくていい。
  • Terraform: 生成AI時代はIaC必須。特に普及しているTerraformはAIと相性がいい。Codeといいつつただの設定ファイルなので怖がる必要はない。プログラミングが苦手でインフラに行った人でも余裕でできる。私はeslint.configのほうが恐ろしい。TSで書けるCDK for Terraformみたいなのもあるが、こんなただの設定ファイルをプログラミング言語で書く意味がない。AI生成で不利なのでいらない。
  • Turborepo: 使い方がシンプル。他にはlerna,Nxが候補だったが、lernaは実質開発終了。NxはNest.jsと同じ匂いがする。全てを飲み込む複雑性の香りだ。
  • Biome: 一番は設定が簡単なこと。そして高速なのでgit hookで実行させてもストレスがない。素晴らしい。
  • Vite: 次世代の標準です。最近はTurbopackやRspackが実用レベルになりつつあるのでどうなるか。
  • Vitest: 良い。最近出たBrowser modeも良い。ただテストに関してはブラックボックステスト原理主義なので、describe, it, expect以外使わない。関数モックとか細かい機能は使ったことない。使わない方がいいと思ってる。
  • Playwright: 良い。小さなテストはVitestのbrowser mode経由で使った方がいいかも。Cypressよりこっちがいいよ。
  • Tailscale: リモートワークのときに大変助かる。

まとめ

失敗した技術の大半に共通することはシンプルでないということ。あと生成AIの影響も大きい。
流行っているものを使うことが昔以上に重要。
なんか内部で怪しいことをしてそうなものは危険信号。あとプラグインだらけとかも怪しいポイント。
エコシステムや生成AI、安定性、将来性を考えると最低限枯れていた方がいいですね。
逆に、良いものが生まれると古いものが廃れていく場合もあるので、新しすぎない程度に新しい物が良いと思います。
新しいものは基本的に古いものより優れています。当たり前ですが。
あと、簡単にすてられるというのは結構重要。
以上はライブラリの話で、簡単に捨てられないDBとかの部分は守りの選択を取った方がいい。PostgreSQLを使おう。

ユーザーの8割は2割の機能しか使わないので、多くのことができると主張しているツールより、一つのことをシンプルに実現できるツールを選びたい。
プロダクトを前に進めることが本質であり、不要なツールの使い方を学習することは本質ではない。(必要なツールの使い方を学ぶのは大事)
技術で遊ぶのは楽しいですが、ふと冷静になってみると、不要な複雑さを持ち込むだけでプロダクトの価値に貢献しない技術を採用してしまっていることは誰しもあると思います。
そんな自分自身との闘い、それが技術選定の本質なのかもしれません。(適当)
一部強い表現があるので怒られないか少し心配です。
読んでいただきありがとうございました。

この記事に贈られたバッジ
参考になった

Discussion

あすぱるあすぱる

私も過去Emotionを使っていたのですが、私の場合はKuma UI が良い選択肢でした

これも乱立するZero Runtimeの一つに過ぎないっちゃ過ぎないのですが、
かなりEmotionに近いことができるAPIがあり、
こんな風に書けます:

import { css } from '@kuma-ui/core';
// ....
<div
  className={css`
    color: red;
  `}
/>

いくつかの使えない機能(interpolationとか)はありますが、
import部分を除けばEmotionと書きかたは基本同じなので、
CopilotちゃんとかChatGPTくんも上手いことやってくれていい感じ
参考になれば……

ramenha0141ramenha0141

9割同意。

Next.js

キャッシュ関係もうちょっといじらせてほしいしCDNとの相性が悪いなど文句がないわけではない。
ただ、SSRを前提とした場合ほぼ一択になるので毎回採用している。
(静的サイトならAstroはあり)
サーバーアクションについては生産性が高く気に入っているが、セキュリティ周りなど理解していないとハマる落とし穴や、毎回認証関係のコードを書くのならいっそHonoでミドルウェアを噛ませてHonoRPCで呼び出したほうが楽なのではと考え始めている。

Tailwind

AppRouter以前はEmotionを使っていて、CSSModulesへの移行も考えたがJSとHTMLを一緒に記述しているのにCSSだけ分離するのは非効率的。
Tailwindはネストするコンテナ全てに名前を付ける必要がなく、AIとの相性が抜群(マークアップとスタイルを同時にいじりやすい)。

MuliMuliSonnanMuliMuliMuliSonnanMuli

セキュリティ周り気にすること多そうだなーと思ってました。CDNのキャッシュから情報漏洩なんて話もありましたね。
サーバーアクションは実は興味があるので参考になります!

CSSだけ分離するのは非効率というのはまじでその通りだと思います。

ideodoraideodora

nest.js は、フロントにangular使ってないと特段恩恵は受けられないす

"The architecture is heavily inspired by Angular."

なので、フロントにangularを採用して裏でnest.jsを構えると、コンセプトが(ある程度)一致して学習曲線が下げられるよ

そうでなければあんま使う意味はないと思う

yosiyosi

Jotai

それ以外の一般的な状態は全てuseStateを使えば済む。

私も会社と個人のプロダクト両方で同じようにしてますね。記述量が増えても最終的に効率良いと感じています。

nextjs

デファクトスタンダードになったフレームワークなので、破壊的変更が減るフェーズになって欲しかったですが、革新的で最高のアプリケーションを作るフレームワークに変わってしまった印象です。
個人開発は振り回されるのが嫌になってAstroに移行しました。Astroはいいぞ。

フシハラフシハラ

nextjsがあんまりピンと来なくて他のJEXを使うフレームワークを試してみたら
SSRのあれやこれやが実はnextjsでしか出来ない事だったと知って驚いたのを思い出した。

nirasannirasan

使用する技術を統一することで生産性も高くなるし、属人化も起きにくくなる。
実際私が仕方なく書いたC++のコードは見事に属人化している。
加えて私がTypeScriptで書いたサーバーサイドのコードの半分ぐらいも属人化している。
なぜかメンバーのキャッチアップが進まない。チーム開発難しい。

結局「使用する技術を統一することで生産性も高くなるし、属人化も起きにくくなる。」と思ってサーバーサイドもクライアントサイドも TypeScript にしてみたけど、なぜかサーバーサイド TypeScript をやりたがる人がいないから生産性上がらないし属人化している。ってこと?

多分既存の Java のコードも残っていて、新たに実装したサーバーサイド TypeScript のコードをチームメンバーがメンテしたがらない、っていうことだよね?

だとしたら筆者が Java をどう思うとかは置いておいて、なぜチームに TypeScript が受け入れられないのか、っていうのが興味あるかも。

現在サーバーサイドで Java と TypeScript が利用されていて複雑になっているとしたら、むしろ TypeScript をあきらめる方がチーム開発としては正しいまであるかもしれないし。

ログインするとコメントできます