1727394704
2024-09-24 13:32:24
過去数か月間、私は自由時間の(ほぼ)すべてを副業に費やしました。 ジャストファックス。すべては LemonSqueezy から Stripe への移行から始まりました (理由を知りたいですか? 他のブログの ニュースレターでは、Merchant of Record ではなく支払いゲートウェイを選択する理由について詳しく説明する予定です)。しかし、あらゆるリファクタリングやリライトと同様に、それは私が予想していたよりもはるかに大きくなってしまいました。簡単な決済プロバイダーの変更では、小さな会計システムを実行するだけでなく、SQL 上にジョブ処理キューを実装する必要がありました。もちろんすべてRustで行います。これにより、これまでマージした中で最大のマージリクエストの 1 つが発生しました。
このマージリクエストはちょうどいいタイミングで届き、私がちょうど 1 年前 (おそらく 11 か月前) に JustFax を始めたことを思い出させてくれます。それに加えて、私は本番環境で Rust を使って作業しています。 $MAIN_JOBそこで、本番環境で Rust を使用するのがどのようなものかを概要を説明することにしました。
コンパイルされれば実行される
これについては私の記事でも簡単に述べましたが、 RustでWebアプリケーションを書いた第一印象、これをもう一度強調したいと思います。
私は通常、行を変更するたびにコードを実行しません (面倒な CSS 調整をしない限り)。ほとんどの場合、私はリファクタリングや機能の実装の大部分を完了してから、すべてを実行してテストします。さらに、Rust ではコンパイルできないコードを実行することができないため、JavaScript とは異なり、コードにエラーがある場合は修正する必要があります。それで私はリファクタリングに夢中になり、数か月にわたって延々と夜をまたいで作業を続けました。あることが別のことを引き起こし、すべてが完了する最後の瞬間まで、コードはコンパイルできる状態にはなりませんでした。
すべてを実行するのは不安でしたが、システムのすべてのコンポーネントをセットアップし、最初の (地元) リクエストがあったのですが、すべてが機能することに驚きました。私は自分が優れた開発者であることを知っていますが、ミスなくコードを書けると自分を騙すことはできません。また、間違ったシリアル化/逆シリアル化形式など、コードの動的側面に関する小さな問題を除けば、コードは完璧に機能しました。
この成功の最初の層は、Rust のタイプセーフでコンパイルされた性質です。誤って割り当てることはできません。 i64 に uuidRustコンパイラではそれができません。また、JavaScript とは異なり、構造体の存在しないフィールドにアクセスすることもできません。そして確かに、経験したことがないと主張する人々もいます cannot access property "foo" of undefined ランタイム エラー、私の経験、そして私の同僚の経験は同じではありません。私たちが Rust を使用する理由の 1 つは、 $MAIN_JOB動的言語で書かれた既存のバックエンドが管理できなくなったためです。確かに、小さなアプリケーションのコンテキストを頭の中に保持することはできますが、ある時点でそれが記憶を超えてしまうことがあります。昔、PHP をやっていたときのことを思い出します。ある時点で、配列にどのようなキーがあるかを知るために、配列の内容を画面上にダンプする必要がありました。いくつかのキーは、 snake_case、一方、他の camelCase、など。
第 2 層は、第 1 層の延長のようなもので、Rust コミュニティの人々が型安全性に対して抱いている強迫観念です。そして、それは良い執着です。なぜなら、それが次のようなツールを生み出すからです。 sqlx — 実際の DB に対してクエリを実行し、クエリの構文が間違っている場合や、 i32 に TEXT 明示的にキャストせずに列を作成します。 SQL は通常、あらゆるアプリケーションの重要な部分を占めており、間違いやすいため、以前は SQL を恐れていました。クエリのスペルを間違えたり、引数を間違えたりしても問題ありませんが、本番環境での幸運を祈ります。多くの人は SQL クエリに関する単体テストを作成しますが、他の人はスキーマからの ORM やコード ジェネレーターの使用に頼ることになります。しかし、 sqlx もう心配する必要はありません。高レベルで非常に抽象化された ORM を使用したり、データベース スキーマからのコード生成に頼ったりすることなく、SQL の能力を最大限に活用できます。確かに欠点はありますが、その一部についてはブログ投稿でさらに詳しく説明します。
このカテゴリに分類できるもう 1 つのツール セットは、次のようなタイプ セーフなテンプレート エンジンです。 askama または maud。私がそれらを試したにもかかわらず、それらは私のプロジェクトに適切な位置を見つけられませんでしたが、それでも、コードから予測不可能な要素をさらに削除します。私を笑ってしまうことの 1 つは、次のようなメールを受け取ったときです。 Hello {{userName}}。名前のないサービスからそれを取得するのは面白いです。銀行から借りるのは恥ずかしいです。タイプセーフでコンパイルされたテンプレートでは、このようなエラーは存在しません。
唯一欠けているのは、タイプセーフなサードパーティ API インターフェイスです。確かに、OpenAPI/Swagger 仕様から OpenAPI クライアントを生成することはできますが、依然としてサードパーティ API プロバイダーのなすがままにされており、彼らが仕様を持っているという事実は、必ずしも彼らがそれに従うことを意味するわけではありません。 API は壊れないので、この問題を解決できるかどうかはわかりません。
走らせてみると非常に安定しています
Rust プロセスのクラッシュを経験したことはありません。ノードプロセスのクラッシュが発生しました。コードを汚染しない限り、 .unwrap() (これは基本的に「結果がエラーの場合はクラッシュする」ということです)、プロセスは決してクラッシュしない可能性が高くなります。いくつか持っています .unwrap() 主に初期化中にコード内で呼び出しが行われるため、構成ファイルが見つからない場合、または環境変数が定義されていない場合は、プロセスをクラッシュする以外にできることはありません。ただし、一般に、Rust ではエラーを明示的に処理する必要があります。を返すものすべて Result、次の方法でエラーをバブルアップする必要があります。 ? 演算子を使用するか、 match それに関する声明。ほとんどの場合、それは理にかなっています。また、場合によっては、少し煩わしいこともあります (たとえば、からの変換がわかっている場合など) i64 に i32 の境界内に十分に収まる数値を保持しているため、成功します。 i32、引き続き行う必要があります try_from() を返す Result)。しかし、たとえ迷惑なケースがあっても、私は頼らない傾向があります。 .unwrap()、むしろ警告をログに記録し、適切なデフォルト値を返します。プログラミングは予測不可能なので、少なくともこの方法では、愚かな可能性がある場所の位置を記録しながら、プログラムを実行し続けることができます。
JustFax は Rust だけでなく、TypeScript やその他の JS フレームワークで書かれた内部システムも備えています。そして、新しい TypeScript プロジェクトを作成するたびに、何かが変わると誓います。何らかのツールの設定ファイルが変更されました(あなたを見ていると) eslint);または、typescript 用の新しい定型文があります。または express はもうクールではなくなり、今では誰もが使用しています fastify;または express は また涼しい;または ts-node それは悪いです、今私たちは使っています tsx;または、このモジュールは esm ただ、これはある間、 cjs それで、図に行きます。 TypeScript には常に何かがあります。作業方法、コードを lint する方法、ワークスペースの動作方法がバニラ JS と比べて異なります。 TypeScript。
しかしRustではそうではありません。
cargo init – ブーム、あなたは新しいプロジェクトを手に入れました。ワークスペースが必要ですか?問題なく、完璧に動作します。リンティング?
clippy カバーしてもらいました。定型文や精神的疲労が大幅に軽減されます。
コンパイル時間は依然として PITA です
間違いなく、Rust の最大の欠点はコンパイル時間です。特に次のようなツールを使用する場合は、 sqlx または maud マクロ(開発中の LSP とコンパイラの両方)に大きく依存するものは、苦戦し始めます。プロジェクトが成長するにつれて、より多くのサードパーティの依存関係を収集します。そして、複数のパッケージ間に共通の依存関係を置くことでそれらを最適化しようとしていますが、ルート Cargo.toml必要な依存関係機能のみを積極的に選択するだけでなく、依然としてコンパイル時間に苦労しています。 CI/CD 内で最初のコンパイルに約 6 分かかっていたのが、現在では約 20 分になっていると述べました。オンラインのさまざまなブログに従って最適化することは可能ですが、多段階の Docker コンテナーに依存関係を積極的にキャッシュする外科手術のような手順が必要です。今は時間がないので、ビジネス面にもっと集中する必要がありますが、それについては後で説明します。
Mac M2 でのローカル コンパイルは、特にインクリメンタル ビルドで管理可能ですが、ストレージの代償が伴います。たまには走ります cargo clean、その結果、数十 GB のキャッシュが削除されます。ただし、インクリメンタル コンパイルを使用しても、JS の世界で得られる即時ホット リロードには程遠いです。最初のポイントに戻りますが、「コードの小さな部分を変更し、すぐにテストする」という開発サイクルは中断されます。 JS/TS で得られるような即座のフィードバックは得られません。それが、JustFax の内部および外部ツールの一部が依然として TypeScript で書かれている理由です。 Rust は、Node/JavaScript のような「1 行変更、Alt-Tab ブラウザー」というワークフローではなく、「大量のコードを作成、コンパイル、チェック」というフローに誘導します。大体それで大丈夫です。本当の型安全性など、多くの見返りを得られます。 CI パイプラインは修正できると思いますが、それに取り組むにはもう少し時間が必要です。
純粋なバックエンドなど、Rust が優れているものもあります。のようなフレームワーク axum API サーバーを構築するために必要な多くのパーツが提供されており、ほとんどの (一般的な) 外部 API には Rust の API 用のクライアントがあります。確かに、これらの API は通常、これらの API を構築する会社によって保守されていませんが、少なくとも私たちはそれらを使用することができます。ただし、Web 上のほとんどのサンプルには Rust が含まれていません。そのため、さまざまな API と統合する方法を自分で判断する必要があります。
これは、Rust での開発では繰り返し起こるテーマであり、少なくとも Web に関しては、他の種類のアプリケーションについてはコメントできません。ソース コードを読んだり、GitHub の問題を参照して、遭遇した問題と同様の問題を探したりすることがよくあります。ほとんどのパッケージは一種のニッチなものであるため、LLM が適切なソリューションに役立つことはほとんどありません。 Rust コミュニティは、パッケージのドキュメントやサンプルに細心の注意を払っており、これは素晴らしいことですが、必然的に、ドキュメントもサンプルも存在しないいくつかの特殊なケースに遭遇することになり、おそらく、内容を理解するためのソースコード。
また、一部のアプリケーションは、特に高速プロトタイピング環境から来た場合には、Rust にあまり適していません。私は今でも、Astro または Svelte を使用して TypeScript でフロントエンドを書くことを好みます。 Rust DX はこの分野ではあまり優れていません。変数を変更するたびにコードを再コンパイルする必要があるため、フロントエンド開発で高速に反復するには時間がかかりすぎます。
結論として、1年前にRustを選んで良かったと思います。それは私が安全を確保するのに役立つだけでなく、 $MAIN_JOB それは本当に気に入っていますが、より良いソフトウェアを構築するのにも役立ちました。そして、Rust の開発 2 年目を楽しみにしています。
#Rust #の運用期間は #年間