- 税関申告書作成アプリケーションは複数のスタックを検討した末、使い慣れた Ruby on Rails のほうが新しい選択肢より体感でずっと速く機能を作れるため、初期実装に使われた
- RustバックエンドとSvelteKitフロントエンドで作った「version 2」は高速で見た目も良かったが、必要な機能は約10%にとどまり、ビジネスコードより配管作業が大きくなった
- 段階的な移行では、ルーティング、データアクセス、権限、変更追跡、検索、バックグラウンドジョブ、pub/sub、メール、OAuth 2.0、SMTP/POP、オブジェクトストレージ、PDF生成まで自前でつなぎ込む必要があり、ユーザー価値をほとんど増やせなかった
- 新しいスタックへ移した部分だけを再びRailsへ戻すことにし、1人でRailsアプリを保守していた状況、ユーザー要求を満たせないストレス、複数箇所を同時に直さなければならない複雑さが判断に影響した
- Rails復帰後は機能開発、既存機能の改善、UI更新、バグ修正の速度が再び上がり、Ruby/Railsの欠点はテスト・リファクタリング・Turbo・キャッシュで対処しながら、プロダクト開発に集中する道を選んだ
Railsを一時的な選択肢として選んだ出発点
- 2022年に税関申告書作成アプリケーションを作り始めた際、.NET/F#、Go、Rust、Reactなど複数の言語とフレームワークを検討し、実際にコードも書いてみた
- 最終的な選択は Ruby on Rails だった
- すでに慣れているツールであり、新しく学ぶ選択肢より体感で10倍速く進められた
- 当時のRails実装は一時的な 概念実証 に近く、後でRubyのようにミスをしやすい言語ではない別の言語で書き直す計画だった
- Railsアプリケーションは素早く機能を増やし、同僚たちも成果物を気に入った
- 生産性を高める機能を素早く作ることができた
- 当初は頻繁に発生していたエラー状況をコードで防げるようになった
RustとSvelteKitの「version 2」が行き詰まった点
- 「version 2」ではバックエンドに Rust、フロントエンドに SvelteKit を選んだ
- 初期版は見た目が良く高速に動作したが、必要な機能は約 10% しか備えていなかった
- RustにはRailsと比較できるような統合フレームワークがなく、ビジネスロジックより基盤コードや接続作業を多く書く必要があった
- 完全なversion 2を作るにはversion 1の開発を完全に止めなければならないことが明らかになり、結局version 2は廃棄された
- 書き直しには元のアプリケーションを最初に作るのと同じくらいの時間がかかり得ることを確認した
段階的移行が生んだ運用の複雑さ
- Rustの強い型、高速な性能、コンパイル成功時の確信から、アプリケーションにRustを入れたいという思いは残り続けていた
- Rubyを4年以上使っていたが、ランタイムの問題も発生し続けた
- 主にnull値と未処理例外が問題だった
- デプロイ後にエラーレポートツールを見ながら、本番環境の問題を素早く修正した
- Railsフロントエンドではロジックとマークアップが複数ファイルに分散する点が不満で、Svelteでは関連ロジックを1つのファイルにまとめやすかった
- 新しいフロントエンドはSvelteKit、新しいバックエンドはRustで少しずつ作成し、既存のRails部分も段階的に置き換えて、最終的にRailsを取り除く計画を立てた
- 序盤はレコード一覧とフィルターを持つ「index」ページを新しいスタックへ移す作業が速く進んだ
- 新しいスタックは高速に動作し、Rust部分ではランタイムの問題がほとんどなかった
- どのルートをRails、Rust、SvelteKitのどれに向けるべきかを決めるデプロイルールが複雑になった
- この複雑さは全面移行までの一時的な状態として受け入れた
ユーザー価値より大きくなった技術作業
- 時間が経つにつれ、Rails側には有用な新機能が継続的に追加され、既存機能もより深く精緻になっていった
- 初期の誤った仮定を直すために複数のリファクタリングを行い、よく設計されたデータベーススキーマはどのバージョンにも役立つと判断した
- Rails UIはいずれ置き換えられる予定だと考え、これ以上多くの時間をかけないことにした
- 数か月後、事業が十分に成功して正規の開発者を雇えるようになり、Rust移行が速まると期待した
- 実際には新しいスタックで多くの時間を 抽象化と仕組み の作成に費やした
- データベースアクセス
- ユーザーが許可されたデータだけを見られるようにする権限処理
- すべての変更の追跡
- 検索とフィルター
- 型システムによる制約の強制
- さらに必要な機能リストも増え続けた
- バックグラウンドジョブ
- クライアント更新のためのpub/subシステム
- メール機能
- 外部サービス連携のためのOAuth 2.0
- SMTPとPOP
- オブジェクトストレージ
- PDF生成
- Rustライブラリは十分にあったが、適切なライブラリを選び、それらをすべて接続する作業が大きかった
- Rust ORMの選択、Svelte 5のリリース時期と改善点といった技術実装の詳細に関する議論が増えた
- 約1年間RustとSvelteを書いた後、ユーザーはこの作業からほとんど利益を得ていないことが明らかになった
- 新しいスタックの一部画面は滑らかで高速だが、もともと問題になっていた部分ではなかった
- 最もよく使われるページは依然としてRailsで動いており、新しいスタックの「ベータ版」より機能が10倍多かった
- 数日または数週間単位で、直接的な価値を追加しない作業に時間を使うことになった
再びRailsへ戻した判断
- 新しいスタックで稼働していた部分だけを Railsで書き直す ことに決めた
- アプリケーション全体を書き直すのではなく、Rust/SvelteKitへ移した部分をRailsへ戻す選択だった
- 判断は難しかったが合理的だった
- すでに多くの時間とコードを投資していた
- チームメンバーがRubyやRailsを好まない可能性もあった
- 幸いチームは判断を理解し、新しく学ぶことにも前向きだった
- Railsの強みは、多くの機能が標準で含まれ、成熟して安定したライブラリエコシステムがあり、規約と意見の強い設計のおかげでコードが簡潔になる点にある
- Railsはプロダクトを作ることに集中させてくれ、下支えするコードを自分で書く負担を減らしてくれる
Rails復帰後に残った教訓
- RustとSvelteの試みは完全な無駄ではなかった
- 新しい言語とフレームワークを学び、他の言語でも役立つ洞察やアイデアを得た
- Webアプリケーション作成は選択と妥協の連続である
- フレームワークの欠陥を受け入れるか、自前実装に投資する必要があり、その投資が競争優位と長期的な見返りをもたらすかを見極めなければならない
- Railsへ戻す作業はかなりの部分で単純で、ストレスも減った
- ここ数週間は新機能開発、既存機能改善、UI更新、バグ修正で再び生産性を感じられるようになった
- RubyとRailsの欠点は、コードを書く際に考慮すべき条件として受け入れる
- ランタイムの問題が多いなら、より多くテストすべきである
- 保守しにくいスパゲッティコードは、言語やフレームワークよりも開発者が作りやすい
- リファクタリングとコード構成により多くの時間を使う必要がある
- Railsのスケールが難しいなら、他のものもスケールさせるのは難しいと考える
- Railsは十分に速く、Turboとキャッシュで回避する方法がある
- ユーザーの立場では、Rustスタックが目に見えて高速というわけではなかった
- Railsで最も重いページの P95 durationは338ms であり、改善の余地はあるが十分に速く感じられる水準である
- 完璧な言語やフレームワークはなく、強みと弱みを理解して扱う方法が必要である
- Railsは良いプロダクト作りに集中したい人にとって非常に優れたフレームワークの1つであり、長く維持され活発なコントリビューターが多い他のフレームワークも同様に良い選択肢になり得る
2件のコメント
Rubyで書き直す
Railsで書き直す
Hacker Newsの意見
Railsは、DjangoやLaravelまで含めても、Webアプリを作るうえで他のスタックよりはるかに先を行っていると思う
退屈な部分をすべて処理してきており、必要な要件を理解していて、実際のアプリを作るときの邪魔が少ない。15年にわたってWebアプリを開発しながら、Meteor、Remix、Next.js、ExpressベースのNode.jsなどを使ってきたが、Web開発はすでにかなり解かれた問題だと感じる。JavaScript界隈は大企業の資金に押されて、開発者が新しいおもちゃをいじる方向に近く、RustやGoはシステム開発・デプロイ基盤にはよいが、Web開発に無理やり押し込むのは筋が通らないと思う
数多くのJavaライブラリと簡単に統合でき、静的型付け言語なのでさまざまな場面で役立つ。Jacksonによる規約ベースのデータバインディングだけを見ても昔から優れていて、動的型付け言語ではこうした部分が後から付け足された感じが強い
しかしRails、Django、Laravelに深く入り込んだ後で規約から外れたことをしなければならなくなると、厄介になり得る。グルーコードを自分で書いていないため、技術的な壁というより理解の空白が生じることが多い。それでも内部をきちんと学べば、コードを読むだけで膨大なコーディング時間を節約できる。もちろん実際にはそうせず、全部書き直すのがあまりに楽しいので、ビジネス的に筋が通らなくても新しい試みを楽しんでしまう
私の意見だけを信じず、データを見ればいい: https://www.youtube.com/watch?v=Qp9SOOtgmS4
ユーザー数や秒間リクエスト数がはっきり分からないサービスにRailsを使うと、ローンチ時点のコストは節約できても、サーバー費用を余計に払うことになり得る
Railsのデモは、採用と注目を得るうえで運がどれほど重要かを示したこと以外には、それほど魅力的ではなかった。今でも価値がよく分からず、SpringかASP.NETを選ぶだろう。そのTclベースのスタートアップの創業者たちはその後OutSystemsを作った。これはエンタープライズ規模のWeb開発で成功した数少ないRADツールであり、ポルトガルIT業界の成功事例の一つでもある
以前はRailsのほうを好んでいたが、この10年以上でPythonが大きく成長し、Djangoのほうがより実用的な選択肢になった。機械学習とデータサイエンスの開発者はすでにPythonに慣れており、Djangoのドキュメントは非常によくできているので、必要ならごく短時間で生産性を出せる。複数のプロジェクトで実際に見てきた
Djangoは、より独断的でないライブラリやマイクロフレームワークに比べて、アプリケーション構造をめぐる決定や議論も減らしてくれる。Django/FLOSSコミュニティも予想よりずっと活発で、やり取りも楽しかった。ただしRailsのようなジェネレーターや組み込みのデータシーディング、たとえば
rake db:seedのような機能があればよいと思うRailsを17年使っていて、今でも好きだし、フロントエンドにも好んで使っている
Angularアプリで見られるように、ファイルの先頭が不要に長い
includes一覧にならない一方で、コードを分離する選択肢はすべて与えてくれる。フロントエンドでロジックとマークアップを分けることに欠点はないと思う。ロジックを独立してテストでき、たいていバグを探すときにマークアップの問題なのかロジックの問題なのか分かるので、追跡しやすくなる。200行を超えるRubyメソッドもかなりたくさん書いてきたが、それは本当に自分の責任で、より細かな単体テスト体系があれば防げるincludes一覧を避けるもう一つの方法は、すべてをグローバルにすることだそれが長所なのかはよく分からない。インポートは場所をかなり取ることがあるが、すべてがどのようにつながっているかを明示的に示してくれる。邪魔ならIDEで折りたたんでおけばよい
現実的な記事でよい。核心となる一文は、いつも枕元に置いて読める価値がある
「興味深いことに、以前RubyとRailsでいら立っていたことが、今ではずっと気にならなくなった。完璧な言語やフレームワークなどないと受け入れた。強みと弱みを理解して扱えばよい」
多くの場合、フレームワークを十分に理解しておらず、なぜそうなっているのかも分かっていない。だが限界を知ったうえでフレームワークと一緒に働くほうが、はるかに効率的だ。自分が使う道具をきちんと理解することに時間を使うべきだ
「その間、アプリケーションの『バージョン2』を作り始めた。バックエンド言語にはRustを、フロントエンドにはSvelteKitを選んだ。」
v1がRailsで始まったのだから、すでにうまく動いているフレームワークを捨てるときは慎重に、という記事として読める
Rust は非常に頭のいい人たちが作った言語だが、焦点は私のような現場の開発者が有用で一貫したものを作ることよりも、型理論や数回のアロケーション削減に寄りすぎているように思える
2020年から Rust を使っているのに、Rust のブログ記事を読むと文字どおり半分は理解できない
巨大な主流言語と比べると特にそうだ。たとえばトレイトは、C++ の抽象クラスとコンセプト、Python の抽象クラスとプロトコルのユースケースをすべてカバーしている。例外を使うか値を返してエラー処理するかという不一致もない。パニックはあるが、アサーションのように使われる。
if-elseが式なら、別途三項演算子も必要ない最近は Rust をあらゆるものに使おうとする流れがあるが、CRUD アプリに使うために作られた言語ではない。この記事は結局のところ、「仕事に合った道具を使え。余暇に好きな言語や最新流行の言語が必ずしも合うわけではない」というケーススタディに近い
ガベージコレクタを許容できるなら、Rust や C++ を使ってメモリ管理を気にする必要はない。Rust を汎用プログラミング言語として宣伝する人もいて、理論上は正しいが、90年代の C++ もそう宣伝されていた。メモリアロケーションを最小化することは、たいてい Rust や C++ を選ぶときに求めているまさにその特性である
Rust で大規模な Web アプリケーションを素早く書くのは難しい
Rails で書いた大規模アプリケーションは高速に動かないこともあり、バグが多かったり、保守や拡張が難しかったりすることもある。Java、C#、Go のような選択肢はセクシーでもクールでもないように見えるかもしれないが、Rails と同じくらい素早く仕事を終えつつ、Rust に近い速度で実行できることもある。たいていはバグも減り、保守や拡張がしやすいプロジェクトになる
Rails をどれほど嫌っていても、これは C++ で WordPress より速くブログを作れると言うのに近い
気を配って作れば必ずしもそうはならない。大規模でバグがほとんどなく、安定していて修正しやすい Rails アプリもたくさん見てきたし、ひどい Rails アプリや Go アプリも見てきた。ある言語やフレームワークはそうした状態に陥りやすくすることはあるが、何にでも可能性はあり、別の理由で難しくなるトレードオフもある。規律があれば何でも動くようにできる
同程度の腕前の開発者なら、ほとんどの Web アプリケーション機能で Rails は Java、C#、Go より速く開発できる。私の経験では最大3倍速かった。大規模モノリスとサービス指向アーキテクチャの両方を扱ってきたし、Java チームとも一緒に仕事をした。かつては同じ信念で Rails 組織に Go を導入したこともあった
結局、本当の問題は MVC がすべてに合うわけではないことと、名前空間を十分に厳格に使っていなかったことだった。小さく対象を絞った領域でその問題を直せば、Rails は十分に問題ない
したがって、十分な管理、良いアーキテクチャ、保守があれば Rails でも可能だと思う
最近、Rails の TypeScript 代替として AdonisJS を試し始めた
「TypeScript 製の Rails」に興味があるなら、一度使ってみることを勧める。最も人気のある TypeScript スタック群よりも Rails や Laravel にずっと近く感じられ、スタックの各部分を自分で選んだりサードパーティのテンプレートを信じたりするのではなく、バッテリー同梱方式だ
最近 Rails、Laravel、Django のようなバッテリー同梱のバックエンドフレームワークに惹かれているが、キャリアを通じてほぼ TypeScript を使ってきたので、今変えるよりそのまま行くほうが簡単だ。試してみた感想が気になるし、言及した他のフレームワークと比較できるかも気になる
最近の個人プロジェクトでは NestJS を選んだが、すぐに Adonis を知り、実際に移行しようか悩んでいる
Next.js や SvelteKit のような技術が JavaScript 開発者の間で人気なのは、Node.js のバックエンド開発体験が良くないという問題を和らげてくれるからだ
TypeScript でバックエンドを作るのは C# で作るのに似ているが、よく設計された言語の利点が抜け落ちている感じがする。Rails でバックエンドを作る体験とは大きな違いがある。Node.js 開発者たちも、問題はエコシステムやツールだけではなく、アプリケーションを作るための全体的な解決策を提供するフレームワークが重要だと気づき始めている。Next.js と SvelteKit が助けているのはその部分であり、Rails が2006年に革新したのもそこだった
バックエンドでコードを実行できることと Rails を比較しているのか? データベースアクセス、マイグレーション、ORM、キュー、予約ジョブ、検証、翻訳、認証、認可のサポートはどこにある? Next.js が助けている唯一のことは、人々を Vercel プラットフォームに押し込むことだけだ
フロントエンドなら JavaScript より TypeScript を好む
Rust が Web バックエンドに向いていないというコメントが少し不可解
beampaint.com や probablyup.net のような小さなサービスを主に作ってきた限りでは、逆の経験をした。もっと大きなアプリを作れば複雑さは大きく増すかもしれないし、おそらくそうなると思うが、大規模なコードベースで必要な自信を与えてくれる点では Rust は優れている
古いフレームワークはそうしたものを標準で備えているが、Rust のフレームワークは歴史が浅いので、その可能性は低い。足りない機能が2つから6つに増えると想像すればいい
この記事はほとんど自分の人生の話のように感じる
Rails を使っていたが、2013年に Go で書いた SaaS アプリをリリースしたところ、すべて自分で調べて解決しなければならなかった。マイグレーション、データベース層、アセットパイプライン、デプロイ、バリデーション、ロギング、決済など、Rails がそのまま提供してくれるものがあまりにも多かった
Go が好きなわけではないが、ここで責めるべき相手は Go ではない。CRUD アプリケーションに Go を使うのは、そもそもひどい判断だ
Django も似たように感じる
パフォーマンス問題は Django 自体よりも データベースクエリ が原因で起きることのほうがずっと多かった
忘れられがちな簡単な最適化としては、データベースインデックスの作成、コードよりもデータベースにより多くのロジックを委ねること、たとえば MongoDB パイプラインや Postgres の部分インデックスの利用、ほとんどのリクエストで Brotli/zstd 圧縮を有効にすること、Redis のようなインメモリキャッシュでオブジェクトやリクエストをキャッシュすること、適切なキャッシュ制御ヘッダーを設定することがある。フロントエンドでは、数千個のオブジェクトを DOM ノードとしてレンダリングせず canvas を使う、といった最適化も必要だ。最近、地図アプリケーションでこうした問題を見た