
30,899 文字

SQLはコンピュータプログラムで使用されることを意図していませんでした。それはレポートを印刷するためのコンソール言語でした。プログラムにSQLを組み込むことは、私たちの業界の最も重大な過ちの一つです。通常、私はこのチャンネルでUncle Bobの意見についてあまり多く語りませんが、今日はこれを取り上げたい理由がいくつかあります。
一つは、この意見がそれほど悪くないと思うからで、そこから多くのことを学べるからです。もう一つの理由は、これが私への返信だからです。私は偶然、Bobが一連の奇妙な反SQL動画を撮影する原因となる狂気的な反SQLの意見を展開させてしまいました。これらの動画からも多くのことを学び、利益を得ることができるので取り上げる必要があります。
しかし、それは完全に混沌としているので、できる限り分析してみたいと思います。ここでは彼の意見の起源、それがどこから来たのか、そして私がそれについて良いと思う点を見ていきます。しかし、SQLから離れるコストを誰かが負担しなければならないでしょう。そのため、まず今日のスポンサーからお話を聞いてみましょう。
今日のスポンサーはFigmaが恐れている会社です。そう、唯一無二の愛すべきLovableです。彼らが素晴らしい製品を作り上げたので、スポンサーとして迎えることができて本当に嬉しいです。彼らは開発者である私たちを本当に理解している数少ないアプリビルダーの一つです。彼らはアクセスできないクラウド上の魔法の環境を作るだけではありません。見てください、それはGitHubボタンです。なぜなら、あなたが行うすべてのことは実際のGitHubリポジトリと同期されるからです。
そのため、あなたのマシンにプルダウンして、好きなように操作することができます。また、コードボタンをオンにして、ここでもコードを見ることができます。これは私が生成させたジョブボードで、素晴らしいものです。これは現状でも実際に使えるものです。印象的です。
これは一発で作られたものでもあります。そして、Oやバックエンドなどのセットアップをしたい場合は、Superbaseにワンクリックで接続するボタンがあります。しかし、これはあなたがすでに知っているLovableではありません。これはLovable 2.0です。彼らはマルチプレイヤー、エージェント型チャットモード、そしてAIアプリでみんなが大好きなセキュリティスキャンを含む大規模な変更をリリースしたばかりです。
そのため、私たちが今見ているようなvibecodedアプリを作成する際に、安全でないアプリを作るのがはるかに難しくなります。そしてもちろん、Figmaを動揺させているもの、デベロッパーモードがあります。コード自体を書きたくない場合は、この小さな編集ボタンをクリックし、UIで変更したい部分をクリックして変更し、保存を押すだけで、コードが更新されます。
また、領域をクリックして特定のことを行うように指示することもでき、それも実行されます。その粒度のレベルは私にとってまだ信じられないものです。これは非常に新しいことです。このようなサイトのほとんどはそのような機能を持っておらず、結果は自ら物語っています。これで美しいものを作ることができます。
私の言葉を信じるか、ホームページにアクセスして、すでに何百もの美しいものが作られているのを見ることができます。これは素晴らしいものを作るための正当な方法です。そしてもしフロントエンドのことをすべて自分で考えたくないなら、これがあります。本当に愛されるものを作りたいなら、今日soyv.link/lovableでチェックしてください。申し訳ありませんが、バスローブを持っていなかったので着用していません。
さて、どのようにしてこの状況に至ったのかを見ていきましょう。私はUncle Bobのこの特定のトークを推薦されました。通常、彼と私はあまり共感しないので心配でした。誰かが私たちが非常に似ていると言っていて、私は混乱していました。
通常、Uncle Bobと私の意見はあまり重なりません。私は「クリーンコード」がソフトウェア開発エコシステムに取り返しのつかない損害を与えたと考えており、クリーンと呼ばれる慣行が、アーキテクチャを完璧にすればバグがなくなるという前提でバグを隠すことが多いと思います。
現実には、バグは存在します。それを見つけて特定しやすくすることの方がはるかに重要です。だから私はそのファンではありません。また、このビデオのカメラを管理していた人がスライドを表示していないことも好きではありません。このビデオではスライドがまったく見えません。混沌としています。とにかく、ここに至った経緯を説明するために、このトークのデータベースセクションにスキップします。
「SQLを書く人はいますか?誰がSQLを書いていますか?申し訳ありませんが、SQLを書かなければならないのは残念です。SQLは、コンソールに座ってデータベースをクエリしたい場合は優れた言語です。しかし、良いプログラミングインターフェースではありません。おそらく最悪のプログラミングインターフェースです。なぜなら、プログラマーはAPIに文字列を渡したくないからです。
彼らはAPIに関数を呼び出したいのです。すべてのデータベースが、恐ろしい構文に合わせて恐ろしいselect whereの文字列を構築しようとするのではなく、関数を呼び出せる実際のAPIを持っていたらどんなに素晴らしいでしょうか。そして私たちは何をするのでしょうか?ORMを作ります。ORMを使って問題を回避します。これにORMはどこにありますか?図が見えると本当に良いのですが、まあ、要点は分かりますね。彼はSQLが悪いと考えています。」
そして、彼はORMを好むと言うでしょう。なぜなら、きれいに分離された抽象化が得られるからです。しかし、そうではありません。「ORMに満足していますか?うわぁ。代替案は何でしょうか?APIです。しかし、APIも結局はSQL文を発行するのではないでしょうか?新しいクエリプロトコルを発明しない限り、同じことをHTTPでラップするだけです。逃げましょう。逃げましょう。」
少なくとも彼は自分の意見が基本的に擁護不可能であることを認識していますが、彼が主張する中核的なポイントは有効です。ここで層を最大限に分解したいと思います。なぜなら、正しいこと、間違っていること、そしてその中間のことがあるからです。
では、できる限り要点を分解しましょう。SQLはデータ端末やコンソールなどには適しています。SQLは特にアプリケーションの開発者には適していません。ORMも悪いです。すべてはシンプルな入力と出力を持つ抽象化であるべきであり、データベースはデータI/Oを公開するAPIであるべきです。
これが彼がこれについて話すときに主張する主要なポイントであり、それらを検討したいと思います。また、Bob Martinが誰であるかについての背景を説明していないことに気づきました。Robert Martinはアジャイルマニフェストの主要な署名者であり影響力のある人物で、SOLIDの原則の発明を手伝い、また大きなOOPの支持者であり、テスト開発、彼が主導して作成したクリーンコードのパターンに力を入れています。
面白いことに、彼はC#についての本を持っていますが、C#を嫌っています。現在は、最近の講演から私が理解する限り、Goのファンのようです。しかし彼はパイプラインの純粋性を好んでいるにもかかわらず、大きなOOPの人でもあります。彼は私を混乱させ、クリーンコードの考え方には多くの欠点がありますが、また彼はここでも良いポイントを作り、これはその価値ある発言の一つです。
彼が今朝私のツイートへの返信として投稿したバスローブの話を見てみましょう。「SQLデータベースがSQL文字列を受け取ったときに内部で何が起こると思いますか?それは内部表現、データ表現にコンパイルダウンし、そのデータ表現を解釈してデータにアクセスするための一連の関数呼び出しを使用します。
以上です。そしてそのデータ表現とそれらの関数呼び出しが、我々プログラマーがデータにアクセスするために使用すべきものです。我々はテレタイプで入力する人々のために50年前に発明された膨大で古風な構文を使用すべきではありません。」テレタイプの参照が好きです。別の時代ですね。注目すべきは、彼は72歳で、トラックのミームで私のツイートに返信していますが、要点は分かります。
現実には、レイヤーは理解が深まるにつれてより複雑になります。私はこれを以前に促そうとしていました。T3チャットの新しい引用機能を使ってみましょう。これを取り上げて、これで新しいチャットを作成します。「このSQLクエリに対して、SQLite内部の関数呼び出しはどのようになるでしょうか?このクエリが実行されたときに呼び出されるSQLite内部コードの例を示してください。」
これは興味深い応答になるはずです。また、最近のお気に入りモデルのO4 miniも試してみます。中程度の設定で大丈夫です。さあ、Gemini 25 Proの応答を見てみましょう。「SQLiteのCライブラリで内部で何が起こるかを分解してみましょう。このプロセスは一般的にAPI呼び出し後、これらのフェーズに従います。
SQLite3_prepare、SQLite3_step。SQLは字句解析され、内部表現(構文木)に解析されます。構文木は、テーブルとカラム名が有効で、データ型が互換性があるかなどを確認するために分析されます。クエリ計画の最適化。SQLはクエリを実行する最も効率的な方法を決定します。ID=42のwhere句では、IDにインデックスがある場合、それを強く使用したいと思うでしょう。ない場合は、フルテーブルスキャンを計画します。コード生成では、クエリ計画はSQLiteの仮想データベースエンジンのバイトコード命令のシーケンスに変換され、VDBはそのバイトコードを実行してB-treeストレージエンジンと対話し、データを取得します。」
SQLは、あなたがSQLクエリを書くと、データを取得するための適切なバイトコードに変換する元のAIだと言えるかもしれません。半分冗談ですが、要点は分かりますね。SQLは、クエリエンジンとプランナーが、あなたが求めているデータを取得するための最も最適なパスを生成するための標準構文として意図されています。
これは理想的な言語でしょうか?ほぼ確実にそうではありません。スキーマとタイプセーフティを管理し、アプリケーションに統合するためのより良いプリミティブがあればいいでしょうか?絶対にそうです。行レベルのセキュリティがどこでも実装されていれば素晴らしいでしょうか?必ずしもそうではありません。しかし、現在の状況を理解する鍵は、これらのデータベースがどのように実装されているかの複雑さが非常に大きいため、それなしで生きるということは、全体的にはるかに悪いものと共に生きることを意味します。
「SQLは元のORMですか?頭が痛くなりそうです。」「ここに指定したステージで関与する可能性のある内部C関数の例をいくつか示します。selectの場合、SQLite3_run_parserがメインのエントリーポイントとなるでしょう。SQL83_get_tokenはトークンを分解します。分析と計画では、SQLite3_selectはSQLiteコードベース内の実際の関数です。」
ここにミラーがあります。良いですね。このミラーをスキャンしてみましょう。あまり幻覚を見ていないことを確認したいです。はい、SQLite3_select関数は実際にあります。いいですね。完全に幻覚を見ているわけではありません。しかし、この関数は何千行もの長さがあるようです。それは面白いですね。
つまり、完全な幻覚の世界にいるわけではありません。それを確認するのは良いことです。しかし、ここで気づくと思いますが、「select ID and name from user where ID is 42」という非常にシンプルなクエリを実行するために、多くのステップを踏んでいます。これらの内部関数を呼び出すためのステップの数は不条理です。
これは完全にBobの意見ではないことは分かっていますが、彼の考えは、すべてのこの上にSQLをインターフェースとしてではなく、私たちがアプリケーション内ですべてこれを自分でやるべきだが、適切な関数呼び出しを持つ抽象化で構築すべきだということです。つまり、database.get_user_by_idのようなものがあり、IDを渡すとそれが返され、SQLの部分なしに下の方で適切なことをします。
Postgresについて聞いてみましょう。これは実際に人々が好むものです。私のMySQLへの愛を嫌っていることは分かっています。私の考えるデータはどこにありますか?O4はどうですか?現在、考えるデータの約50%を提供しています。これはかなり煩わしいことですが、少なくとも応答を得ることができます。「PostgreSQL_main関数は最終的にexec_simple_queryを呼び出します。」
これが楽しくないことが分かると思います。このようなものを書くのは全く楽しくありません。そのため、システムに直接影響を与えるものを書くべきだという考え全体に対して私は躊躇しています。また、そのAPIを公開することが特に理にかなっているとは思いません。しかし、ここでもう少し深く掘り下げる必要があります。SQLの利点についてです。SQLが実際にうまくいったことがたくさんあり、それがその成功につながったと思います。それらを理解することで、失敗部分を理解する助けになると思います。
最大の利点は、ほとんど標準化されていることです。そのため、SQLite、Postgres、MySQL、Cassandra、およびその他のSQLのような構文の間を切り替えるのはそれほど難しくありません。それは標準的であり、多くの異なる実装で動作するツールを構築することを可能にしました。さらに、SQLレイヤーとデータベースの残りの部分の間でクレイジーな最適化を可能にしました。
アプリがあり、そのアプリコードがSQLコードと接続し、そのSQLコードが実際のDB実装にアクセスする場合。標準としてのSQLのクールな点は、業界として私たちに自由を与えてくれたことです。Planet ScaleやVitestチームのようなMySQLからシャーディングするための取り組みなど、クレイジーなことを見て行うことができました。これにより、このコードが実際にデータを多くの異なるボックスに分割して、可能な限りパフォーマントにする方法を変更することができました。
行レベルのセキュリティやウェブソケットレイヤーを追加するなど、データベースレイヤーにクレイジーな機能を追加することができます。そのため、SQLコードが hit すると、他のクライアントに更新を送信することができます。データにアクセスするための標準構文としてのSQLは、DBとSQLの間だけでなく、アプリコードとSQLの間のORMやその他の革新的なことでも、クレイジーなことを可能にしました。
これらは両方とも本当にクールであり、Bobが大きな抽象化の人であることは知っています。彼は特定のアプリケーションに関して抽象化のポイントを考えすぎていると思います。しかし、これを私たちのアプリではなく、業界全体のアプリとして考えれば、これらの抽象化は業界全体のイノベーションを可能にしてきました。
とはいえ、私がよく話してきたことがあります。私はTwitchチャットで特別なコマンドでこれを持っていました。GraphQLはサーバーとユーザーの間に位置し、サーバーとデータベースの間ではありません。GraphQLはAPIをクエリする方法であり、グラフデータがアプリケーションを通じてより簡単に流れるようにするための標準的なRESTフェッチングの代替として意図されています。
ユーザーオブジェクトがあり、ユーザーに友人がいる場合、友人はユーザーである友人オブジェクトのリストであり、自分自身のネストされたプロパティを持っています。これはアプリケーションコードでネストされた適切なものを選択する方法です。GraphQLは特定のもの、特定のスケールに対して本当にクールです。
特に、データベースとサーバーの間、あるいはさらに悪いのは、従来のサーバーなしでデータベースとユーザーの間にGraphQLを投入することに対しては、本当にクールではありません。いいえ、GraphQLは私たちがここで話している問題の解決策ではありません。
しかし、SQLの性質上、SQLとDBの間にGraphQLのようなものを置き、それが間違っていることに気づいて捨てることができます。そして、この抽象化が歴史的に私たちにとってうまく機能してきた理由、そしてSQLが1970年代から50年以上にわたって成功してきた理由です。この抽象化により、多くのイノベーションが可能になりました。
彼の見解では、SQLがここにあるのではなく、DB管理サービスアクターがあり、データベースの詳細を呼び出す関数とメソッドを持つ抽象化をここに構築すべきだということです。しかし、今やこのレイヤーは消えています。なぜなら、このクラスはDBの実装の詳細に大きく依存しているからです。彼は、これを別のものに交換できるはずだと主張しています。そうはいきません。
このクラスは、データベースレイヤーの実装に非常に痛みを伴うほど結びつけられることになります。そして、抽象化を好む人として、彼がなぜ構文が好きではないこと以外の理由で、抽象化レイヤーとしてのSQLを好まないのか少し混乱しています。彼はブログ記事でもう少し詳しく説明しています。
これはBobby Tablesのブログ記事です。Bobby Tablesを知らない人のために、これは古典的なXKCDコミックです。私はこれについてあまりにも多く考えています。「こんにちは、これはあなたの息子の学校です。コンピュータに問題があります。」「ああ、彼は何かを壊しましたか?」「あなたは本当に息子をRobert’; DROP TABLE students;と名付けたのですか?」
「ああ、はい、私たちは彼を小さなBobby Tablesと呼んでいます。」「今年の生徒の記録を失いました。あなたが満足していることを願います。そして、データベースの入力をサニタイズすることを学んだことを願います。」古典的なものですね。彼はこれについて2017年にブログ記事を書いています。このXKCDが登場した時期(2007年10月10日)からすると、このブログ記事は10年以上後のものですが、まだ古く感じます。
2017年は8年前です。要点は分かりますね。とにかく、「SQLは悪魔の産物であり、自尊心のあるソフトウェア開発者は決して使用すべきではありません。」これは少し大げさです。悪魔がSQLを作ったわけではありません。実際、それを作った人々は良い意図に満ちていました。しかし、地獄への道は良い意図で舗装されているとも言います。
テキストデータアクセス言語を使用することがいかに最高に悪い考えであるかを考えてみてください。そのような言語はシステムのユーザーインターフェースを通過し、含まれているすべてのデータへの不正アクセスを提供する可能性があります。ブラウザでのHTMLのレンダリングの仕組みを知るまで待ちましょう。
もちろん、私たちは皆、潜在的なSQL注入、SQL攻撃のために、すべての入力をスキャンすることになっています。それでも、何十万、いや何百万ものユーザーが、まさにこのメカニズムによってデータを盗まれています。なぜでしょうか?すべてのシステムのすべてのユーザー入力が必要な保護を持つことを期待するのは非合理的だからです。
もちろん、この問題は何十年も前に根本的に排除されていたかもしれません。1998年に、rain.for.puppyはfrackの中で、ユーザーインターフェースを通じてSQL文を実行するためにどのように入力するかを説明しました。その記事が公開された瞬間、世界中のすべてのプログラマーはその場でSQLの使用を中止すべきでした。
SQLがまだ使用されていることに私は絶対に驚いています。EquifaxやYahooから、あるいはほとんどすべての企業からは何も学ばなかったのでしょうか?そして、ここで私たちは現在のフレームワークが問題を処理するだろうと自分自身を慰めています。Hibernateが問題を処理します。JPFが問題を処理します。Railsが問題を処理します。収益化を解除したくないので言いませんが。
フレームワークは問題を処理しません。プログラマーが処理します。そして、プログラマーはその問題をうまく処理してこなかったのではないでしょうか?一つのアイデアがあります。SQLの使用をやめましょう。SQLは究極のセキュリティ侵害です。SQLはシステムのユーザーインターフェースを通じて送信でき、SQLエンジンに渡された場合、システム内のすべてのデータへの絶対的なアクセスとコントロールを提供できるポータブルで普遍的なテキスト言語です。
SQLステートメントがユーザーインターフェースを通じて入ってきてRAMに保持される可能性があるという考え自体が、あなたを無限の恐怖で満たすはずです。適切な保護を提供する正確な秘儀を思い出すことに失敗するような哀れな馬鹿なプログラマーがいるだけです。これらのステートメントのうち、どれがSQL攻撃に対して脆弱かを言えますか?言語はRubyです。フレームワークはRailsです。
user_email = email_pars_email.
User.where(“email = ‘#{email}'”)
User.where(“email = ?”, email)
User.where(email: email)
これは文字列として安全でない方法で渡すため、それはパースされて適切にサニタイズされる変数として渡されます。Rubyでは安全でないパスがどれほど不明確かという点で、これはSQL構文の問題ではなく面白いです。しかし、ORMが私たちを十分に保護していないという点は理解しています。SQL自体が脆弱なものです。
しかし、それはSQLだけではありません。このブログ記事はHTMLで書かれたページにあります。コメントセクションはありますか?ないと思います。ユーザー入力を受け付けていないと思いますが、多くのウェブサイトはそうしています。そのため、このHTMLページがあり、その上に段落、ユーザー提出コンテンツがあり、ここにユーザー提出コンテンツをレンダリングしたいとします。ユーザー提出コンテンツが私たちが実際に望まないものである場合はどうなりますか?
ユーザー提出コンテンツが「sub nerds」などであれば、それはうまくレンダリングされます。しかし、「LOL</p><script>alert(‘nerds’)</script><p>」だったらどうでしょうか?このユーザー入力をサニタイズせずに盲目的にレンダリングすると、大きな問題が発生します。なぜなら、ユーザーにPタグを閉じ、スクリプトタグを開き、何かをさせることを許可したからです。単にアラートを出すだけならそれほど悪くありませんが、何らかのクロスサイトスクリプティングのためのソースを与えると、はるかに悪くなります。
これは非常に簡単に行われるミスです。そして、この問題に対する対応は「HTMLは最悪で、決して使用すべきではない」というものではありません。HTMLがブラウザにどのように入れられるかを考慮する必要があります。Reactのようなフレームワークはこれをデフォルトで処理します。サニタイズされるプロパティとして渡す以外に実際にHTML値を直接設定したい場合、これがアプリケーションでこの種のバグを有効にする方法です。
ここでのリスクは非常に現実的です。それは「データベース全体へのアクセス」というのと同じではありませんが、ある意味ではより悪いです。なぜなら、もしブログにコメントを残し、そのコメントが example.com/myhostilejs スクリプトにリンクしている場合、コメントがレンダリングされている限り、ページをロードするたびにこのスクリプトが実行され、通常は実行できないことを実行できます。
例えば、HTTP only クッキーがページにある場合、JavaScriptはそれらのクッキーにアクセスできません。しかし、ページ上でJSを実行できれば、それらのクッキーでAPIコールを発行できます。そのため、このフォーマットでコメントを残すだけで、アカウントを乗っ取ったり、リセットをトリガーしたり、それらのクッキーで使用するAPIがあればメールアドレスの変更をトリガーしたりできます。
さらに悪いことに、このスクリプトタグに要素を隠す何かを追加することもできます。そうすれば誰もそれを見ることはなく、ただ背景で静かに実行されます。これらの入力の問題点は、それらをコントロールできないことです。そしてBobがシステムについて考えるやり方は、すべてを制御することを前提としています。
これはここだけでなく、他の場所でも問題があります。昨日、Internet of Bugsチャンネルでこの素晴らしいビデオを見つけました。私はサブスクライブするのを忘れていたので、今修正しています。ここにはクリーンコードの痛みを伴うポイントのとても良い例があります。仮想的な経費報告アプリを想像してみてください。
ユーザーが合計25.13ドルのレシートを提出したが、レポートには25ドルちょうどと表示されたというバグチケットを想像してください。13セントの差額はどこに行ったのでしょうか?
私はこの問題の変種を経験したことがあります。正確にこれではなく、経費報告書ではありませんでしたが、すぐにわかる理由で金額が間違っていたという同様の問題でした。
この議論の目的のために、経費報告書についてのように話しますが、これは実生活で実際に遭遇した問題です。私が見ることができる25.13ドルが25.13ドルとして表示されず、13セントがどこに行ったのかを探しています。
セントが切り捨てられる場所、あるいは四捨五入される場所を探します。どこにもそのようなものは見つかりません。何が起きているのかわかりません。数日間この問題と戦っても、まだ本当のところは分かりません。そして、仕様や会計マニュアルを見ていると、問題の日の日当は25ドルだったことに気づきました。
25.13ドルが25ドルに切り捨てられていたのではなく、システムがこれらのレシートは関連がないと判断したため、25.13ドルが完全に無視されていたことが判明しました。なぜなら、その人はその日に日当を受け取ることになっていたからです。この特定のケースでは、これが日当タイプの経費報告書なのか、レストランからのレシートを見るタイプの経費報告書なのかをプログラムが判断する条件が、その特定の状況では間違っていました。
これは特に悪質なバグの一種であり、特に「クリーンコード」の109ページと110ページにある技術によって引き起こされています。これらをスクリーンに表示します。なんと素晴らしい動きでしょう。これはこれらの理由で悪いと言い、本の特定のページで議論されています。
私は自分自身を説明できますが、正直なところInternet of Bugsがとても上手に説明しているので、彼に任せましょう。「これが行うことは、日当の金額かレシートの合計金額のどちらかを返し、プログラムの他の場所にどちらを選んだかを伝えないということです。
クリーンコードはこの時点で、なぜその決定をしたのかをプログラムの残りの部分から隠すように明示的に指示しています。これはエラー処理の名の下に行われています。これは私を激怒させます。彼らは、潜在的なエラーを処理する正しい方法は、バグを見つけるのに関連するかもしれない情報を取り、このクラスのプライベート部分の中に隠されているため、コードの他の場所からその情報へのアクセスを防ぐことだと言っています。
しかし、ここにあるものがあちらにあるものに影響を受けるべきではないという基本的な基本原則を破っています。なぜなら、ここととあちらの間に線を引き、このすべてのものをこれらのプライベートクラスに隠し、プログラムの残りの部分が情報を見ることを許さないからです。これはクリーンコードに入る哲学の完全な失敗であり、正直なところ、見るプログラミングアドバイスのほとんどがそうです。
コードを単純化することでより綺麗にできると仮定しています。それは間違っています。なぜならその仮定は、単純化しているコードが永遠に100%正しいことが100%保証される世界でのみ有効だからです。そうではありません。100%のテストカバレッジがあっても(私が遭遇したケースではほぼそうでした)、それは要件を理解したことを意味するわけではありません。
それは要件が変わっていないことを意味するものでもありません。それはあなたが書いたコードが、当時あなたが持っていた理解に一致することを意味するだけです。そして、このケースでは、多くのケースと同様に、コーナーケースがありました。元のプログラマーの理解が間違っていた状況がありました。
おそらく最初の仕様自体が間違っていたかもしれません。それがトリガーされて気付くまでに時間がかかりました。」このビデオ全体が素晴らしいです。全部を見るためにここに座っているつもりはありません。リンクは説明に入れますので、自分でチェックしてみてください。はい、Internet of Bugsはここで素晴らしい成果を上げ、多くの重要なポイントを作りました。
特に、バグは避けられないという考え方です。私たちはバグを見つけて修正しやすくするようにシステムを構築すべきであり、それらを抽象化して存在しなくするべきではありません。世界はそのようには機能しません。そして正直なところ、SQLの攻撃の議論に関しても同じように感じます。この問題は解決しました。SQLは50年間存在しています。
私たちは入力をサニタイズするための方法を多すぎるほど持っています。それは独自の問題を引き起こすほどです。以前、Adamはサーバーアクションが初めてNextcomで紹介されたとき、「await sqlinsert into bookmarks (slug) values (${slug})」のようなform actionのuse serverコールの例をツイートしました。
これはスラグに好きなものを挿入できるため、非常に危険に見えます。しかし、これは単に文字列を渡しているわけではありません。関数のラッパーが側にないことに注意してください。これはテンプレートリテラルであり、最初に文字列(この部分が何であれ)を渡すことで動作する関数呼び出しですが、これらのセクションをセグメント化し、残りは引数配列です。
そのため、それは最初の文字列とすべての引数で呼び出される関数です。それでこれらを一緒にマップし、入力をサニタイズする独自のコードがあります。そのため、これは従来のSQLコールのように見えますが、実際は完全に問題なく安全です。ここでの返信の全員がセキュリティの問題について騒いでおり、それ以来一貫したミームとなっています。
彼はそれがエスケープされており、これは問題ないと知っていますが、哀れなSamを悩ませると永遠に続くことが面白いです。人々はSQLインジェクションをほとんど恐れすぎています。個人的には、彼が言ったことに戻ります。追加するのは「SQLは大きなリスクです。人々は十分にサニタイズしていません。」というポイントです。
これはほとんど解決された問題であり、SQLに対する議論には必要ないと思うので、これを削除します。彼が以前に頼っていた杖のように感じます。また、彼が最近のSQLに対する攻撃でSQLの攻撃角度を全く持ち出さなかったことも注目に値します。
これはより古い見解であり、現在彼が擁護するかどうか分かりませんが、業界としてこの問題をほぼ解決したと感じています。ユーザーの意図を入れた生のSQL文を文字列として書いて実行しない限り、SQLインジェクションよりもXSS攻撃を持つ可能性の方がはるかに高いです。
そしてこれらのタイプの悪用は、あなたが何かが受け取る前にサニタイズされているかどうかを知らないため、適切にクリーンコード化されたシステムではるかに簡単に遭遇します。それは混乱します。私は彼に同意したいと言いましたが、多くの良いものがあります。私の合意はどこにあるでしょうか?
まず何よりも、SQLはデータターミナルに適しています。私は過去から多くのクレイジーなデータ人を知っています。そしてそれらの人々の中には、SQLで信じられないことができる人々がいます。私が開発者として理解できる構文でデータベースにデータを書き込むことができるORMレイヤーを持ち、そしてデータチームが同じデータベースに入ってこれらのクレイジークエリを実行し、分析モードを行うことができるという事実は素晴らしいです。
データチームがターミナル、コンソール、分析モードツールでデータにアクセスし、私が同じ構文にコンパイルされる異なる構文でそれを書くことができるレイヤーを持つことは実際に本当にクールです。それは大きな勝利であり、私はユーザーについて特定の特徴を見つけようとするときに人々がしばしば実行しなければならないクレイジーなネストされた関係クエリに対してSQLがどれほど優れているかを見ることができました。ここでは確かに同意します。
SQLは開発者に適していません。ここは少し曖昧ですが、正直なところ、同じ理由ではないにしても、このポイントに徐々に近づいてきました。この構文で何が好きではないか知っていますか?挿入の代わりに選択だと想像してください。
私が好きではないのは、このコールの結果が常にanyや不明としてタイプする必要があることです。なぜなら、文字列からデータベースに何があるのかを知ることができないからです。これがDrizzleのようなものをクールにしている理由です。DrizzleはTypeScript開発者のための「OM」です。多くの点でクエリビルダーに近いです。
構文を見ると、ホームページには何もないかもしれませんが、このセクションがあります。Drizzleは嫌いです。申し訳ありませんが、Drizzle。あなたは最悪です。彼らの呼びかけが好きです。彼らが構築したのは、非常にSQLのような構文db.countries.leftJoin(…).where(…)です。スキーマを見ると、それはTypescriptにあり、型推論はすべてスキーマのTypeScript定義を介して行うことができます。
そして、彼らのCLIを使用して移行を実行するためのSQLコードを生成することができます。移行は非常に悪い抽象化であり、それ自体がアプリケーションコードがSQLを使用すべきではない理由の議論を作ります。なぜなら、アプリケーションの状態はコードとデータベース内のデータによって表現されるべきであり、データベースの形によって表現されるべきではないからです。
彼らはアプリケーションコードとデータベースコードの間で同期する必要があります。理想的には、その同期ステップは必要なく、二つの間の同期を維持するためにこの移行プリミティブは必要ありません。これらが一緒に動作するために構築されていなかったことを示すような抽象化の一つです。
これが書くクエリをフードの下で見ることができます。これは標準的で退屈な純粋なSQLにコンパイルされますが、型安全の保証も得られます。そして入力を渡すと、それもサニタイズしてくれます。これがDrizzleについて好きなところです。TypeScriptで型安全性を持ってSQLを書くことができます。
そのため、システムとアプリケーションの一部のように感じます。アプリケーションコード、型システム、データベースの関係がより綺麗に結びついています。私がこれを好きなのは、Bobがおそらく嫌うことですが、スキーマに入ってnameからfull_nameまたはusernameに変更しても、starを選択してユーザーに渡すAPIで型エラーが発生しません。
コンポーネントでそれをレンダリングしようとすると型エラーが発生します。型はデータベースで定義されている場所からレンダリングされているコンポーネントまで尊重されるからです。userame(ユーザー名)をレンダリングしていて、バックエンドでこのフィールドを変更すると、レンダリングしている場所で型エラーが発生します。これはとても素晴らしいです。
戻って、アプリケーションで生のSQLを書くことは悪いことに同意します。データの構造と実際のアプリケーションコードの間の関係を提供しないものもまた同様に悪いと言えるでしょう。SQLの利点を得るためには、そのギャップを埋める解決策が必要です。
セキュリティの意味や他のことのためにSQLが悪いとは思わないので、ここでは中立ですが、アプリケーションで直接SQLコードを書くべきではないとは思いません。そして移行は奇妙な抽象化です。私たちはgit履歴に状態の履歴を持っています。移行ファイルにもそれを必要としません。
そして私が完全に同意しない最初の意見は「ORMは悪い」です。彼がこれについてもっと詳しく説明してくれればと思いますが、図に戻ると、アプリがSQLコードに直接アクセスしていない場合、ORMにアクセスし、そしてORMがSQLにアクセスすることになります。彼の議論は、ORMがここでは不要であり、代わりにデータベースの実装に直接アクセスすべきであり、SQLはここでは無用な中間言語だということです。
私はそれに同意しません。なぜなら、複数のアプリがある場合どうなるでしょうか?ユーザーアプリがあるとして、データ分析や緊急対応ダッシュボードもある場合はどうでしょうか?もしこのSQL層がないなら、これらすべてが私たちのORMレイヤーを通過しなければならず、それは煩わしいです。なぜなら、データ分析チームはアプリケーション開発チームと同じプログラミング言語を使いたくないからです。そしてそうする必要もありません。
分析チームは自分たちが好むことをできるべきです。緊急対応ダッシュボードはできるだけ維持しやすく、実装の詳細にあまり直接結びついていないべきです。なぜなら、もし緊急対応者がデータを管理するパッケージの障害に対処している場合、彼らは情報を得ることができないからです。だから、これらすべてがここを通過することは意味がありません。
だからこそ、現在はそうなっていません。実際に起こることは、これらの異なるレイヤーがSQLにアクセスし、ORMは一つまたは複数の異なるレイヤーがSQLにアクセスするために使用するかもしれない抽象化に過ぎないということです。しかし、私たち全員がSQL層を持っているため、異なる場所がデータにアクセスでき、もしこれらすべてが以前見たような恐ろしい呼び出しを実装しなければならないなら、データをフェッチするためにすべてのこのコードを書かなければならないなら、いや、もっと良く知っています。この教訓を学びました。もし各チームが何かにアクセスするために独自のアセンブリを書いていたら、
奇妙なロックや恐ろしいパフォーマンスになります。より良いコードになることはありません。これは機能しません。これはまったく意味がありません。悪いORMは存在しますか?はい、確かに。ほとんどのORMがかなり悪いとさえ言えるでしょう。Active Recordはアプリケーション開発者がデータについて考える方法を非常にポジティブな方法で変えました。
そしてその理由でそれについて話したくありませんが、Bobのブログで見たようなイシューがあり、なぜそれが私の意見では非常に防止可能なミスからあなたを防ぐための十分な抽象化ではないかを示しています。だから私はここで彼に同意せず、彼が抽象化レイヤービット以外にORMについて何が好きではないのかをもっと詳しく説明してくれればと思います。なぜなら、私たちが使用するプリミティブとしてのSQLは、Cにコンパイルする言語と同じ方法で理にかなっていると思うからです。
次は「すべてはシンプルな入力と出力を持つ抽象化であるべき」です。これを二つに分けましょう。「すべては抽象化であるべき」、「いいえ」。「シンプルな入力と出力」、「はい」。私は関数型プログラミングが好きで、彼が特に最近のトークでこれをフレーミングしている方法です。彼は少し関数型プログラミングのパイプラインに落ちかけているようで、それは興奮させます。
しかしポイントは、データベースアクセスレイヤーは、それが意味をなすまでアプリケーション固有の抽象化であるべきではないということです。最近好きなパターンは、DBオブジェクトを作成し、クエリキーとミューテーションキーを持たせることです。そして何かを呼び出したいときは、DB.queryを呼び出します。
ここではDB.query.getImages WithTagsやdb.mutate.deleteUserData、resetUsage、updateSubscriptionなどがあります。これらはすべて型安全でもあります。なぜなら、すべてこれらのDrizzle呼び出しを直接呼び出しているからです。しかしこれはいわゆるSQLです。私はこれを非常にSQLっぽい方法で書いています。その結果、ここには呪われたものがあります。SQLは完璧からほど遠いからです。getImagesWithTagsコードを見てみましょう。はい、getImagesWithTags、SQLiteの欠陥と関係の悪さのため、内部的に結合する奇妙なことをしています。SQLiteの周りの騒ぎを理解していません。
意味のある関係をSQLiteで行おうとするたびに、モデル全体が崩れ始めます。しかし、ここでは画像が持つタグを取得しようとしており、これを合理的な方法で取得するためにJSONでグループ化する必要があります。それはうるさいです。しかし、ポイントはSQLを書いて型安全な結果を得て、このインライン部分を解析する必要があるということです。
ありがたいことに、これはユーザー入力を受け取っていませんが、もしそうなら、安全を保つために引数として渡す必要があるでしょう。「ああ、いいえ。いいえ。これはテンプレート化されています。これは安全です。ここにユーザー情報を渡すことができ、それは安全でしょう。」とにかく、煩わしいです。とはいえ、データベースとのインターフェースのすべての方法を定義するオブジェクトのアイデア、それは良いアイデアであり、私はその抽象化が好きです。
私はいつもその抽象化が好きではありませんが、アプリケーションに意味がある場合、データにアクセスする標準的な方法があるのはクールです。しかし、それはデータベースレイヤーであるべきか、それとも一段階高いか低いレベルであるべきでしょうか?良い質問です。
言いたくありません。なぜなら、私たち全員がこれらのタイプのことについて独自の決定を下すべきだからです。多くの異なるオプションと、ものを構造化するための多くの異なる方法があります。一つの方法だけを規定すると、かなりの数の人々が失敗するように設定することになります。なぜなら、彼らにとって最も意味をなさないからです。
できるだけシンプルに保ち、複雑な問題を解決するために必要な場所に複雑さを追加するべきです。データパイプラインの内部関数を呼び出すデータベースアクセスレイヤーを自分で構築することは、シンプルな方法ではありません。単にそうではありません。Bobの意見に戻りましょう。「データベースはデータI/Oを公開するAPIであるべき」
いいえ、ここでは同意しません。データベースにより良いAPIがあるべきだと思います。絶対にそうです。そしてSQLは異なる時代のために構築され、だからこそそれが人気になったのです。しかし、これらのものに対するIOの内部動作は狂っています。ORMの抽象化により、データベースはクレイジーなイノベーションと変更を行うことができました。
ORMの抽象化のおかげで、ReddisからPlanet Scale、SQLiteプロバイダーに数時間で移行することができました。データベースの抽象化レベルが低すぎるため、これらの変更を行うことができず、SQLの標準的な性質により、Reddis以外のものを切り替えるのがはるかに簡単になりました。
ReddisからSQLへの移行は大きな作業でしたが、SQLの世界に入れば、他のものへの切り替えは比較的簡単でした。データベース技術がアプリケーションの土地でどのように私たちとインターフェースするかについてもっと考えることを好みます。そして、これをより多く見始めています。GelやPostgress Unchainedのような会社があります。Gelは素晴らしいです。
もともとEdgeDBとして知られていました。ご存知の方もいるでしょう。彼らはついに意味のあるものにリブランドしました。彼らが構築したのは非常に異なる構文です。使用する必要はありませんが、お勧めします。「select movie { title, actors: { name } } limit 5 filter rating > 3.0」。これはSQLではとても煩わしいものを書くための素晴らしい方法です。
TypeScriptクライアントを使用して、TypeScriptでも行うことができます。ここでの目標は、アプリケーションでインターフェースする方法により密接に関連したものを構築することです。制限を適用できる関係であるネストされたキーのアイデアは非常にアプリケーション的です。それはある意味でGraphQLのようにも感じますが、正しい意味においてです。なぜならこれはこれを中心に構築されたデータベースだからです。
Gelのアドバイザーであることも開示すべきです。彼らをたくさん手伝ってきました。だから私には株式があります。だから、ここでの私の意見には少しバイアスがあるかもしれません。バイアスを持って考えるべきもう一つはConvexです。現在、私は彼らの株式を持っておらず、本当にそれを変えるべきです。なぜなら彼らをたくさん助けてきたし、彼らが構築しているものにとても興奮しているからです。
これはデータベースについての別の考え方であり、コードが抽象化であり、それはあなたのコードベースに単に存在しています。私はT3チャットのサーバーサイドコードをデータ用にほぼ完全にConvexベースに書き換える途中であり、それは素晴らしいものでした。TypeScriptコードであるスキーマがあります。
スレッドはスレッドID、タイトル、作成日などのフィールドを持つテーブルです。要点は分かると思います。より迅速に検索できるようにインデックスを定義します。そして、クライアント、サーバー、その他の場所でヒットするエクスポージャーされた一連の関数であるスレッドファイルがあります。クエリにはオプションの入力があります。
認証プリミティブが含まれていることを確認するために、独自のoクエリラッパーを作成しました。そして「return await context.db.query(‘threads’).withIndex(‘by_order’).take(200)」を返します。ここでクールなのは、これらすべてのawait呼び出しを一つのトランザクションにフラット化し、一度だけ発火するトランザクションレイヤーで実行されることです。これは超クールです。
現在、彼らはすべてMySQL上に構築されており、素晴らしく動作しています。パフォーマンスはナッツです。しかし、彼らがこの抽象化を構築したため、それは仕事をするのに素晴らしいだけでなく、TypeScriptのthreadsをホバーするとすべての型情報が得られるという型安全なものであり、特定の関数に移動するためにAPI threads getをコマンドクリックすることもできます。これはとても素晴らしいです。
しかし、彼らはその抽象化を持っているため、その上に同期を構築することもできます。これで、従来のHTTP呼び出しの代わりにウェブソケットを介して接続できます。そして、データに変更が発生すると、トランザクションレイヤーの動作方法のおかげで、特定の変更によってどのデータが影響を受けるかが分かり、ユーザーはページを更新する必要なく変更を受け取ることができます。
テストスレッドを作成してみましょう。「テストスレッド了解しました。」準備はいいですか?素晴らしい。さて、私はこの新しいスレッドを作成しました。ここで魔法が起こります。開発環境に移動し、データに移動して、スレッドを見つけ、テストスレッドを見つけます。いいですね。名前を「購読を忘れずに」に変更します。今、UIが更新されました。
私は更新しませんでした。何もトリガーしませんでした。ただ即座に更新されました。この抽象化は実際に非常に素晴らしいです。なぜなら、通常私を狂わせる従来のデータ同期問題について心配する必要がなくなったからです。これにとても満足しており、とても興奮しています。
その結果、コードベースの多くの煩わしいことが簡素化されます。サーバー上で、タイトルを変更するミューテーションを起動でき、クライアントが更新を取得することについて心配する必要はありません。サーバー上でコードを呼び出し、クライアントは更新を取得します。これは進行中の作業です。無視してください。
理論的には、これをもっと早く検証していたはずです。まだやっていません。重要な部分は「server.convexClient.mutation」です。APIの内部chat.updateThreadを渡します。これは内部APIです。これにはアクセスできません。私だけがサーバーを通じてこれにアクセスできます。スレッドID、検証されたスレッドID、ユーザーID、ユーザー識別子、更新された本文、タイトル。
これは私のアプリケーションインターフェース、私のAPIを通じてデータへのミューテーションであり、これが発火して成功すると、クライアントは更新を取得します。自分で再フェッチするためのものを構築する必要はありません。現在行っているように、ストリームダウンする必要はありません。現在のT3チャットの実装では、このレイヤーに多くの作業を入れる必要がありました。それは狂気です。
このcreate Message関数は、しばらくの間私の悩みの種でした。618行あります。これはいくつかの異なる関数ですが、このファイルはこのプロジェクトの作業の半分が行われる場所です。ストリームヘルパーがあり、物事の状態を同期して取得した後、このフェッチコールを発火します。このフェッチコールは、時間の経過とともにイベントを送信するポスト呼び出しであるため、SSEレスポンスを返します。
そのため、ラインごとにコンテンツをストリーミングバックしています。次に、データストリームを処理する必要があります。私は自分の実装を使用していました。ついにストリームを処理するためにVersellからSDKに移行しましたが、自分のものをたくさん書く必要がありました。そしてこれがすべての重要な部分です。テキスト部分では、インデックスDBのローカルインスタンスを追加し、更新します。推論部分では、代わりに推論チャンクを更新します。
データ部分では、データが来る多くの異なる条件があります。UIのタイトルを更新する必要があるプロバイダーメタデータがあります。UIの他の部分を更新する必要のあるレート制限があります。私はここでは行っていません。レート制限は以前とは少し異なる働きをします。
更新されたタイトルを取得するために解析しているスレッドメタデータがあります。画像生成は異なるデータケースであり、テキストとは異なるパイプを通るため、これはエラーケースとは異なるレンダリングをする必要があります。別のエラーケース、別の完了部分があります。
これらはすべて、サーバーからのあらゆるチャンクが当たる可能性のあるすべての異なるケースのためのものです。代わりにサーバー上でこれらすべてを実行し、ケースをヒットし、データベースを更新し、ユーザーが更新を見るなら、これらすべてを取り除くことができます。もはや文字列を使用して異なるケースを解析し、戻ってきたJSONの値が正しいことを願う必要はありません。
互いに安全でないこれらすべての抽象化は捨てることができます。エラー処理の場合、ここでJSONを解析していなかったケースを見つけました。これは、エラーメッセージが間違っていて、常にキャッチケースに当たるということを意味していました。つまり、エラーが初期応答フェーズではなくストリームフェーズで発生した場合、実際に発生したエラーではなく、「エラーが発生しました」と常にレンダリングされます。
そして、まず私がこれらすべてについて考えなければならないという事実は、なぜみんなが「このようなアプリを構築するのは簡単だ」と言うのが間違っているかの理由です。しかしより重要なのは、リクエストの形の性質のために持たなければならない抽象化であり、より良いデータベース抽象化があれば、それを完全に取り除くことができます。
そのため、データベースについて現在考えている方法がアプリケーションの実際の実装の詳細にうまくマッピングしないという点では大いに同意します。しかし、それはデータを直接書き込む関数呼び出しが必要だからではありません。より高いレベルの抽象化が必要だと思います。一つ上のレイヤーに上がると思います。
そして、その一つ上のレイヤーで、突然多くの機能とコントロールが得られ、物事を単純化することもできます。彼の最後の意見、彼はビデオでこれを言いました。「データベースは宇宙の中心であるべきではない」というフレーミングです。そこでは完全に強く反対します。実際、業界として私たちが行った最も良いことの一つは、私たちの状態であるデータベースがあるという考えです。
状態が私たちのロジックを通過し、そこから私たちのアプリケーションが出てきます。私は偏見があります。関数型プログラミングが好きだからです。これは良いことです。これを書く別の方法は「アプリケーション = 関数(データ)」です。これは良いことです。これにより生活がはるかに簡単になります。なぜなら、ユーザーにバグがある場合、バグの場所を追跡するのがはるかに簡単だからです。
データベースは悪い状態にありますか?それはおそらくバグの源です。データベースは良い状態にあり、アプリケーションレイヤーで何か別のものが出てきますか?アプリケーション側でそのロジックが始まる場所から戻り、一歩一歩戻ってデータベースに戻るまで追跡しましょう。
そうすれば、物事がどこで間違ったかをはるかに簡単に把握できます。そして大規模なアプリケーションでは、この単純さは素晴らしいです。これを説明するために、ある特定のコミュニティからこの構文を借用しています。これはReactがどのように紹介されるかです。UIは状態の関数です。状態が変わると、関数を再実行し、新しいUIが出てきます。
Bobが代わりに提案する方法は、データベースが中心であり、すべてがそこから流れるのではなく、アプリケーションが中心であるという点で非常に異なります。しかし、アプリケーションは今や少し異なります。なぜなら、アプリケーションにはたくさんの異なる部分があり、それらはすべてここにあり、それらは異なるロジックを抽象化し、これらはすべてのさまざまな方法で互いに呼び出すことができるからです。
これがいかに混沌としているかがわかります。少し混乱していますが、これらのサービスにはそれぞれ独自の抽象化があるためクリーンです。オンボーディングコントローラーは請求サービスがどのように機能するかを気にする必要はありませんが、ユーザーがユーザー管理サービスでの更新を連絡する前に請求書を支払ったかどうかを気にする必要があります。
最悪の部分は、データベースがまだ存在する必要があることです。そして結局、これらの各部分がデータベースに連絡する必要があり、プロセスで絶対的な混沌をもたらします。これは実際には起こらないと言わないでください。今まで見た大規模なクリーンコードアーキテクチャのコードベースはすべてここに行き着きます。
宇宙の中心にデータベースがある点は、それが中心にあるということではありません。これを2次元のグリッドのように考えるよりも、一次元のパスとプロセスとして考えることを強くお勧めします。アプリケーションをデータベース、ロジック、アプリケーションのように上から下にマッピングできるなら、すべてがはるかに理解しやすくなります。
なぜなら、ここでバグがある場合、それがどこで発生したかを一行一行遡って追跡できるからです。ここでバグがある場合、それがどこから来たのかを追跡することは地獄になります。それは殺人ミステリーであり、実際のデバッグ体験ではありません。これは良くありません。このように世界を考えると、アプリではなくデータベースを中間に置くことは非常に愚かに聞こえます。
そして彼が戦っているのは、この混沌の中にデータベースが中心にあるべきだという考えです。そしてその点では同意します。この混沌の中にいる場合、すべての中心にデータベースがある、良くありません。しかし、クリーンというよりも純粋性について考え、データベースの変更がどのようにその下のすべてを吹き飛ばし、更新されたアプリケーション状態がデータベースから来て、ロジックを通り、アプリケーションが出てくるかを考えると、物事ははるかに簡単になります。
もし、これらすべての部分を移動させるとしましょう。例えば、ユーザーが何かからチェックアウトしたとします。チェックアウトは請求サービスに行く可能性があります。請求サービスはこのプロセスでユーザー管理サービスを更新する必要があるかもしれません。そのため、請求サービスは「これは新しい請求情報です。これが変更されたことを知るために、ユーザー管理サービスに入れましょう」と言います。
それがデータベースを呼び出します。素晴らしい。今、出てくるのは新しいアプリケーション状態です。更新と組み合わせて、Convexのようなこれを処理するものを使用していない場合、データベースの更新です。まだ同じ部分があると仮定します。
理想的には、コントローラーなどではなく、単なる関数です。データベースの変更により、オンボーディングコントローラーは異なる状態を持ち、これにより異なるアプリケーション状態がレンダリングされます。アプリケーションをこの方法でアーキテクチャ化すると、デバッグがいかに簡単になるか理解できますか?特にこれ全体が関数としてフレーミングされている場合。
これが私がFPを好きな理由です。なぜなら、このように考えると、デバッグは不安定さとバグがはるかに簡単になるからです。そして実際にはチェーンはそれほど長くありません。チェーンはユーザーがチェックアウトをトリガーし、請求サービスコントローラーが発生し、ユーザー管理サービスがDBを更新するというものです。次に起こることは、ここの部分は別です。
データベースの変更後、更新をトリガーするか、または終了したときにユーザーがページに移動します。さて、ユーザーがページを開くとき、ページが開かれたときに効果的に関数を呼び出します。関数は必要なデータについてデータベースにクエリを実行します。そのデータをオンボーディングコントローラーとその他のレイヤーに渡し、アプリケーション状態が出てきて、アプリケーションが出てきます。
このように考えるとはるかに良いです。しかし、すべてのための明確な入力と出力が必要です。例えば、サイト上の特定のURLのエントリーポイントには、データを取得し、そのデータを正しい関数に順に渡し、結果が出るという明確な関数がある必要があります。
これもReactの仕組みです。Reactが優れている大きな理由の一つです。なぜなら、データベースが入ってきたときのオンボーディングコントローラーの状態について考える必要がないからです。オンボーディングコントローラーが更新をトリガーするまでにユーザー管理サービスに保存されているデータは何ですか?はるかにクリーンであり、業界はこの方向に動いています。
Reactはその大きな部分です。一般的にMVCに対する反発があります。信じられないかもしれませんが、私はボブと私がこの点について非常に一致していると思います。私たちのアプリケーションは任意にモデル、ビュー、コントローラーに分割されるべきではなく、機能、ルート、ユーザーが実際に行うことによって分割されるべきです。その点については非常に一致していますが、入力と出力のクリーンさは私がはるかに気にすることであり、データベースは宇宙の中心ではありませんが、入力ポイントではあります。
データは関数に入って私のサービスに入るのではなく、関数が呼び出されたときにデータベースにあるか、データベースを更新し、更新された状態をトリガーするだけの関数を呼び出すことによって入力されます。コントローラーには状態があります。コントローラーには独自の状態があります。オンボーディングコントローラーの代わりにdoOnboardingFlowだった場合、新しいコントローラーを作成することになります。
コントローラーの代わりに、これを入力を取り、新しい出力が出てくる関数と考えました。コントローラーがデータベースに行き、一部の情報を取得し、それをモデルを通してビューに渡し、それからUIの状態が更新されることを可能にするというものとは非常に異なります。このフレーミングが好きです。アーキテクチャに適用されるOOP対関数型です。はい。
大きな違いは、これらの部分のどれも状態を所有していないことです。ユーザー管理サービスはサービスではありません。それはデータベースを更新する呼び出すことができる一連の関数です。請求サービスコントローラーはむしろonCheckoutのようであり、これはユーザーがチェックアウトをトリガーすることからの入力を取り、それを変更したり何かをしたりして、それをupdateUserStateに渡します。
そして、これらのどれもその中に独自の状態を持っていません。これらはすべて単にロジック部分です。それは良いです。ロジックはデータ入力を取り、データベース内の他のデータの部分を更新し、クリーンな出力が出てきます。まだバグが存在する可能性は絶対にあります。このように構築しても、まだバグが存在する可能性は絶対にあります。
違いは見つけやすいことです。このように構築し、新しいユーザーデータが入るときに古いユーザーデータを正しく永続化しないというユーザー管理サービスに奇妙なバグがあった場合、それは純粋な関数呼び出しでデータベースからではなく、独自の状態のある実体であるため、そこで何が起きたのかを理解することがはるかに難しいです。
もしそれが独自のものに抽象化されていた場合、これが起こったことを理解することははるかに難しくなります。モックコードを書きましょう。これを、ユーザーがチェックアウトを完了するときにヒットするエンドポイントのようなものとして考えることができます。チェックアウト情報を持っています。ユーザーIDを取得します。ユーザーをデータベースから取得します。ユーザーがいない場合、エラーをスローします。チェックアウトバリッド。
チェックアウトバリデートを待ちます。チェックアウトが有効でなければ、エラーをスローします。更新を待ってください。ユーザーID、クレジット、チェックアウトメールを送信し、更新をトリガーするか何かを行います。これが私が説明している方法です。そして、もう一方の端では、このようなものがあります:exportasyncfunction、uh、renderhomepage。
すでにここでユーザーIDを渡して検証したことにしましょう。それを自分で行うことさえそれほど難しくありません。クッキーなどから取得するでしょう。しかし、ここでは、もしユーザーがいなければエラーをスローします。ユーザークレジットのコストを返します。div creditsなどが帰ります。要点は分かると思います。これはOOPで非常にクリーンなコード的な方法で行うのと非常に異なります。
このようなコードを書くのは嫌いです。そのため、大企業にそれをやってもらいましょう。「OOPのクリーンコードの方法で最高の実践を示すためにこのコードを書き直してください。」問題が見えると思います。CheckoutServiceクラス、プライベートなuserRepository、プライベートなcheckoutValidator、プライベートなemailService。これらすべてを構築する必要があります。
これらの公開関数を作成し、実際に何かをしたいときに呼び出す必要があります。彼らはこれを持っていたと思います。はい、ここでコメントアウトされたコードがあり、これは使用方法です。userRepositoryはデータベースUserRepoオブジェクトです。checkoutValidatorはSimpleCheckoutValidatorオブジェクトです。emailServiceはSCESEmailServiceという新しいオブジェクトです。
checkoutServiceはCheckoutServiceオブジェクトです。これらはすべて独自の状態を持っており、いつでもどのような状態にもなることができます。そのため、チェックアウトリクエスト関数が呼び出されると、チェックアウト情報でcheckoutService.processCheckoutを呼び出します。これが今どのような状態にあるのかわかりません。
これがすでに実行されていて、何を実行したのかわかりません。今、内部にある現在の状態はどうなっているのかわかりません。まだユーザーリポジトリを持っていますか?このユーザーのためのものですか?それとも別のユーザーのためのものですか?これらすべてがそこでそれぞれ異なる状態になる可能性があります。そして、その詳細は開発するにつれて私たちから隠されています。
このプロセスチェックアウト関数を見つける必要がありますが、それは物事が実行された順序を全く教えてくれません。コンストラクタがいつ呼び出されたのか、何で呼び出されたのか、イベントの順序は全くわかりません。この関数をじっと見つめ、システムについて深く考え、バグがここで発生したかどうかを推測する必要があります。これは良くありません。
アプリケーション設計のOOPパターンが特に良くない理由について説明する素晴らしいビデオがたくさんあります。なぜなら、アプリケーションは多くの点で本質的に一時的だからです。サーバーコードは長い間実行されているかもしれませんが、ユーザーのそのサービスとの特定のセッションは、これらの状態のあるオブジェクトでいっぱいのやり方で扱うべきではありません。
状態の追加インスタンスはバグのための追加のサービスエリアです。そして、より減らすことができればするほど、チェックアウト情報のような状態から始め、データベーストランザクションが出てくるか、あるいはもう一方の方法ではデータベースからデータを取得し、値も取得し、結果をレンダリングします。
これは物事が間違ったときにバグを特定するはるかに簡単な方法です。このコードはおそらくあまりクリーンではなく、確かにテストするのは難しいですが、物事が間違ったときにデバッグするのははるかに簡単です。誰かが「Reactコンポーネントもネストされていて、独自の状態を持っている」と言いました。はい、しかしそれらはトップダウンであり、コンポーネントが持つ状態はアプリケーションの実行または親から来なければなりません。
そして親が何かの値を変更すると、その値はツリーを通じてすべての way を通過します。これがReactが行うことの美しさの一部であり、状態の変更がチェーン下のすべてを更新することをトリガーし、反対方向にデバッグするのをはるかに容易にします。ゲームは複雑な状態のあるオブジェクトを持つ長い寿命のプロセスであり、奇妙な相互作用があると認めます。
OOPはゲームには本当に良いですが、少なくともその多くには。しかし、ページをレンダリングするアプリケーションには良くありません。なぜなら、状態はリフレッシュを押したときからコンテンツが表示されるまでの間だけ存在する必要があるからです。それ以外の場合、それは永続化されます。そして、データベース内のデータをユーザーのUIに変換するパイプにアプリケーションを変えられるほど、維持、デバッグ、反復がより簡単になります。
そしてそれがConvexが本当にクールである理由です。なぜなら、クライアント状態とサーバーのデータベースの理解の間のギャップを減らすからです。クライアントとサーバーが同期していない可能性を減らします。そして、これらの同期していない瞬間は、アプリケーション設計における多くの問題の根源です。
また、この大量のサービスの問題は、ユーザーリポジトリがチェックアウトバリデータで期待するよりも少し異なる状態にある場合、何が起こるかということです。これらすべてのものが独自の状態を持っている場合、それらは同期から外れる可能性があります。そして同期から外れると、生活は必要以上に難しくなります。私たちの仕事は、これらの間の状態が完璧であることを確認することではありません。
私たちの仕事は、データをユーザーのための体験に変えるパイプを構築することです。最後に触れたいのは、Convexのことです。「Convex APIを使用してデータをクエリおよび管理することは、Uncle Bobが望むことと正確に一致しませんか?ユーザーとして、彼らがデータをどのように保存および取得するかわかりません。それが理想的ではないでしょうか?」
なぜなら、これは彼がこのタイプのことで目指していることだからです。チェックアウトサービスを呼び出すとき、何が起こっているのかわかりません。それがその清潔さです。チェックアウトサービスの実装の詳細を気にする必要はありません。これを何と呼びましたか?しかし、バグがある場合は気にする必要があります。そしてそこでそれが崩れます。
Convexについて私が好きなことの一つは、それらの詳細についてあまり気にする必要がないことです。なぜなら、それをただのデータとして扱っているからです。しかし、それが機能する理由は、Convexが機能を持っていないからです。「convex.updateUserinfo」のようなものはありません。ロジックを自分で書く必要があり、それをコマンドクリックして見ることができます。
そして、すべてのロジックはそこで一撃でトップからボトムまで行われる必要があります。Convex内に関数とデータを持つ状態のあるエンティティはありません。そして、Bobがここで求めていたものという観点からより重要なのは、データベースへの直接アクセスがないことです。なぜなら、ConvexはMySQL上に独自のRustの抽象化を構築したからです。
彼はこの二つのレイヤーを取り除きたいのです。彼はRustの抽象化とMySQLを取り除きたいのです。そして代わりに、私たちが直接データベースを呼び出すレイヤーを持ちたいのです。そうすれば、アプリケーションコードで独自のデータベースサービスを構築できます。それは意味がないと思います。
彼はさらに深く進み、現在SQLがある場所に抽象化を構築してほしいと思っています。私はより高く上がりたいと思っています。そうすれば、アプリケーション設計で推論するのがより簡単な抽象化がSQLの上にあります。それが私たちの意見の違いです。しかし、最終的には非常に似た結論に達します。
私はこの一つの主要なポイントでボブに完全に同意します。アプリケーションコードで直接SQLを書くべきではありません。これをやるべきではありません。デモでは楽しいです。ものを説明するのに楽しいです。一度きりのことには楽しいです。しかし、アプリケーションコードで直接SQLを書くべきではありません。
私はボブに、なぜこれを考えるのかを詳細に説明する機会を与えてくれたことに感謝しています。たとえ私たちがなぜそうなのか、そしてそこに至る実装の詳細について完全に意見が異なっていても。
SQLは間違った抽象化だということに同意します。それはアプリケーションの開発者としての私たちのニーズを理解していません。しかし、データベースは意味がある場所では真実の源であるべきです。アプリケーション内の状態の量を可能な限り減らすべきです。そして、入力から体験への簡単なパイプになりうるすべてのものをそのようにするよう努力すべきです。
私の言葉を鵜呑みにする必要はありません。データから体験へ行く簡単なパイプを構築するというこの共有ビジョンに集まった、より賢い多くのエンジニアがいます。そして、クリーンコードはそこに当てはまらないと思います。結果として同じ敵がいたとしても、私たちは一致していないと思います。
ここで言われたいくつかのことは少し不条理でもあります。「ORMは悪い」「すべてが抽象化であるべき」「データベースはアプリの設計方法を主導すべきではない」。SQLがアプリケーションコードに書くべき間違ったものであるという中核的な合意があるにもかかわらず、これらのポイントの大部分に同意しません。
いつものように参加してくれてありがとうございます。うまくいけば、アプリケーションでのSQLについて何か学んだことでしょう。そうでなかったとしても、少なくとも視聴していて楽しい時間を過ごせたことを願っています。あなたの考えを教えてください。次回まで、コードはきれいに保ってください。
コメント