コーディング演習: Nodejs のデータベース移行ツール
要件
次の特性を持つデータベース移行ツールが必要です:
- すべての移行は単一の SQL ファイルに書かれており、これは「アップ」部分と「ダウン」部分の両方を意味します。これにより、Copilot がロールバック移行を実行できるようになります。また、これが裸の SQL であるという事実により、最も柔軟でサポートされているソリューションになります。
- 現在適用されているバージョンはツールで管理する必要があります。ツールを自立させたいです。
- このツールで Postgres、MySQL、SQL Server などのさまざまなデータベースをサポートしたいので、その意味で拡張可能である必要があります。
- サイズが大きくなりすぎないようにするため、必要なデータベースのドライバーのみを、理想的にはオンデマンドでインストールする必要があります。
- 私が取り組んでいるほとんどのプロジェクトは JavaScript エコシステムの一部であるため、これを JavaScript エコシステムの一部にしたいと考えています。
- すべての移行はトランザクション内で実行する必要があります。
導入
これらのポイントの多くは、tern と呼ばれるこの素晴らしいツールを使った私の経験から生まれました。 JavaScript には同じ機能がないのが残念でした。 (あるいは、私がグーグルするのが苦手なのかもしれません...)。そこで、これは自分自身にとっては素晴らしいコーディングの練習になるし、他の人にとっては興味深いストーリーになるかもしれないと判断しました:)
発達
パート 1. ツールの設計
CLI ツールの設計を盗みましょう!
- すべての移行には、
_ .sql という名前付けスキームが適用されます。ここで、番号は移行バージョン番号を表します (例: 001_initial_setup.sql)。 - すべての移行は単一のディレクトリに存在します。
- データベース ドライバーは、事前にバンドルされたパッケージか、ある種の npm install
を発行するだけで、オンデマンドでダウンロードされます。
ツールの構文は次のようになります。 martlet up --database-url
「上」はまだ適用されていないすべての移行を適用する必要があり、「下」は指定されたバージョンにロールバックする必要があります。
オプションの意味とデフォルトは次のとおりです:
- database-url - データベースの接続文字列。デフォルトでは環境変数 DATABASE_URL を検索します。
- driver - 使用するデータベースドライバー。最初のバージョンでは、「pg」という名前のオプションを使用して Postgres のみをサポートします。
- dir - マイグレーションが存在するディレクトリ、デフォルトはマイグレーションです
ご覧のとおり、実際のコードを記述する前に、ツールを呼び出す方法を考えることから始めました。これは良い習慣であり、要件を実現し、開発サイクルを短縮するのに役立ちます。
パート 2. 実装
2.1 解析オプション
それでは、まず最初に! Index.js ファイルを作成し、ヘルプメッセージを出力してみましょう。次のようになります:
function printHelp() { console.log( "Usage: martlet up --driver <driver> --dir <dir> --database-url <url>", ); console.log( " martlet down <version> --driver <driver> --dir <dir> --database-url <url>", ); console.log( " <version> is a number that specifies the version to migrate down to", ); console.log("Options:"); console.log(' --driver <driver> Driver to use, default is "pg"'); console.log(' --dir <dir> Directory to use, default is "migrations"'); console.log( " --database-url <url> Database URL to use, default is DATABASE_URL environment variable", ); } printHelp();
次にオプションを解析します:
export function parseOptions(args) { const options = { dir: "migrations", driver: "pg", databaseUrl: process.env.DATABASE_URL, }; for (let idx = 0; idx < args.length; ) { switch (args[idx]) { case "--help": case "-h": { printHelp(); process.exit(0); } case "--dir": { options.dir = args[idx + 1]; idx += 2; break; } case "--driver": { options.driver = args[idx + 1]; idx += 2; break; } case "--database-url": { options.databaseUrl = args[idx + 1]; idx += 2; break; } default: { console.error(`Unknown option: ${args[idx]}`); printHelp(); process.exit(1); } } } return options; }
ご覧のとおり、私は解析にライブラリを使用していません。単に引数リストを反復処理して、すべてのオプションを処理するだけです。したがって、ブール値オプションがある場合は反復インデックスを 1 シフトし、値を持つオプションがある場合は 2 シフトします。
2.2 ドライバーアダプターの実装
複数のドライバーをサポートするには、データベースにアクセスするためのユニバーサル インターフェイスが必要です。以下にその様子を示します:
interface Adapter { connect(url: string): Promise<void>; transact(query: (fn: (text) => Promise<ResultSet>)): Promise<ResultSet>; close(): Promise<void>; }
connect と close は非常に明白な関数だと思います。transact メソッドについて説明しましょう。クエリ テキストを受け取り、中間結果を含む Promise を返す関数とともに呼び出される関数を受け入れる必要があります。この複雑さは、トランザクション内で複数のクエリを実行する機能を提供する一般的なインターフェイスを備えている必要があります。使用例を見ると理解しやすいです。
アダプターが postgres ドライバーを探す方法は次のとおりです。
class PGAdapter { constructor(driver) { this.driver = driver; } async connect(url) { this.sql = this.driver(url); } async transact(query) { return this.sql.begin((sql) => ( query((text) => sql.unsafe(text)) )); } async close() { await this.sql.end(); } }
使用例は次のようになります:
import postgres from "postgres"; const adapter = new PGAdapter(postgres); await adapter.connect(url); await adapter.transact(async (sql) => { const rows = await sql("SELECT * FROM table1"); await sql(`INSERT INTO table2 (id) VALUES (${rows[0].id})`); });
2.3 オンデマンドドライバーのインストール
const PACKAGES = { pg: "postgres@3.4.4", }; const downloadDriver = async (driver) => { const pkg = PACKAGES[driver]; if (!pkg) { throw new Error(`Unknown driver: ${driver}`); } try { await stat(join(process.cwd(), "yarn.lock")); const lockfile = await readFile(join(process.cwd(), "yarn.lock")); const packagejson = await readFile(join(process.cwd(), "package.json")); spawnSync("yarn", ["add", pkg], { stdio: "inherit", }); await writeFile(join(process.cwd(), "yarn.lock"), lockfile); await writeFile(join(process.cwd(), "package.json"), packagejson); return; } catch {} spawnSync("npm", ["install", "--no-save", "--legacy-peer-deps", pkg], { stdio: "inherit", }); };
最初はyarnを使用してドライバーをインストールしようとしますが、ディレクトリ内に差分を生成したくないので、yarn.lockファイルとpackage.jsonファイルを保存します。糸が利用できない場合は、npm に戻ります。
ドライバーがインストールされていることを確認したら、アダプターを作成して使用できます。
export async function loadAdapter(driver) { await downloadDriver(driver); return import(PACKAGES[driver].split("@")[0]).then( (m) => new PGAdapter(m.default), );
2.4 移行ロジックの実装
まず、データベースに接続して現在のバージョンを取得します。
await adapter.connect(options.databaseUrl); console.log("Connected to database"); const currentVersion = await adapter.transact(async (sql) => { await sql(`create table if not exists schema_migrations ( version integer primary key )`); const result = await sql(`select version from schema_migrations limit 1`); return result[0]?.version || 0; }); console.log(`Current version: ${currentVersion}`);
Then, we read the migrations directory and sort them by version. After that, we apply every migration that has a version greater than the current one. I will just present the actual migration in the following snippet:
await adapter.transact(async (sql) => { await sql(upMigration); await sql( `insert into schema_migrations (version) values (${version})` ); await sql(`delete from schema_migrations where version != ${version}`); });
The rollback migration is similar, but we sort the migrations in reverse order and apply them until we reach the desired version.
3. Testing
I decided not to use any specific testing framework but use the built-in nodejs testing capabilities. They include the test runner and the assertion package.
import { it, before, after, describe } from "node:test"; import assert from "node:assert";
And to execute tests I would run node --test --test-concurrency=1.
Actually, I was writing the code in a sort of TDD manner. I didn't validate that my migrations code worked by hand, but I was writing it along with tests. That's why I decided that end-to-end tests would be the best fit for this tool.
For such an approach, tests would need to bootstrap an empty database, apply some migrations, check that database contents are correct, and then roll back to the initial state and validate that the database is empty.
To run a database, I used the "testcontainers" library, which provides a nice wrapper around docker.
before(async () => { console.log("Starting container"); container = await new GenericContainer("postgres:16-alpine") .withExposedPorts(5432) .withEnvironment({ POSTGRES_PASSWORD: "password" }) .start(); }); after(async () => { await container.stop(); });
I wrote some simple migrations and tested that they worked as expected. Here is an example of a database state validation:
const sql = pg(`postgres://postgres:password@localhost:${port}/postgres`); const result = await sql`select * from schema_migrations`; assert.deepEqual(result, [{ version: 2 }]); const tables = await sql`select table_name from information_schema.tables where table_schema = 'public'`; assert.deepEqual(tables, [ { table_name: "schema_migrations" }, { table_name: "test" }, ]);
4. Conclusion
This was an example of how I would approach the development of a simple CLI tool in the javascript ecosystem. I want to note that the modern javascript ecosystem is pretty charged and powerful, and I managed to implement the tool with a minimum of external dependencies. I used a postgres driver that would be downloaded on demand and testcontainers for tests. I think that approach gives developers the most flexibility and control over the application.
5. References
- martlet repo
- tern
- postgres driver
以上がコーディング演習: Nodejs のデータベース移行ツールの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

現実世界でのJavaScriptのアプリケーションには、フロントエンドとバックエンドの開発が含まれます。 1)DOM操作とイベント処理を含むTODOリストアプリケーションを構築して、フロントエンドアプリケーションを表示します。 2)node.jsを介してRestfulapiを構築し、バックエンドアプリケーションをデモンストレーションします。

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。

PythonとJavaScriptには、コミュニティ、ライブラリ、リソースの観点から、独自の利点と短所があります。 1)Pythonコミュニティはフレンドリーで初心者に適していますが、フロントエンドの開発リソースはJavaScriptほど豊富ではありません。 2)Pythonはデータサイエンスおよび機械学習ライブラリで強力ですが、JavaScriptはフロントエンド開発ライブラリとフレームワークで優れています。 3)どちらも豊富な学習リソースを持っていますが、Pythonは公式文書から始めるのに適していますが、JavaScriptはMDNWebDocsにより優れています。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

開発環境におけるPythonとJavaScriptの両方の選択が重要です。 1)Pythonの開発環境には、Pycharm、Jupyternotebook、Anacondaが含まれます。これらは、データサイエンスと迅速なプロトタイピングに適しています。 2)JavaScriptの開発環境には、フロントエンドおよびバックエンド開発に適したnode.js、vscode、およびwebpackが含まれます。プロジェクトのニーズに応じて適切なツールを選択すると、開発効率とプロジェクトの成功率が向上する可能性があります。

CとCは、主に通訳者とJITコンパイラを実装するために使用されるJavaScriptエンジンで重要な役割を果たします。 1)cは、JavaScriptソースコードを解析し、抽象的な構文ツリーを生成するために使用されます。 2)Cは、Bytecodeの生成と実行を担当します。 3)Cは、JITコンパイラを実装し、実行時にホットスポットコードを最適化およびコンパイルし、JavaScriptの実行効率を大幅に改善します。

JavaScriptは、Webサイト、モバイルアプリケーション、デスクトップアプリケーション、サーバー側のプログラミングで広く使用されています。 1)Webサイト開発では、JavaScriptはHTMLおよびCSSと一緒にDOMを運用して、JQueryやReactなどのフレームワークをサポートします。 2)ReactNativeおよびIonicを通じて、JavaScriptはクロスプラットフォームモバイルアプリケーションを開発するために使用されます。 3)電子フレームワークにより、JavaScriptはデスクトップアプリケーションを構築できます。 4)node.jsを使用すると、JavaScriptがサーバー側で実行され、高い並行リクエストをサポートします。
