"यदि कोई कर्मचारी अपना काम अच्छी तरह से करना चाहता है, तो उसे पहले अपने औजारों को तेज करना होगा।" - कन्फ्यूशियस, "द एनालेक्ट्स ऑफ कन्फ्यूशियस। लू लिंगगोंग"
मुखपृष्ठ > प्रोग्रामिंग > कोडिंग अभ्यास: नोडज में डेटाबेस माइग्रेशन टूल

कोडिंग अभ्यास: नोडज में डेटाबेस माइग्रेशन टूल

2024-11-04 को प्रकाशित
ब्राउज़ करें:315

Coding exercise: database migration tool in nodejs

आवश्यकताएं

मैं एक डेटाबेस माइग्रेशन टूल चाहता हूं, जिसमें निम्नलिखित गुण हों:

  1. प्रत्येक माइग्रेशन एक एकल SQL फ़ाइल में लिखा जाता है, जिसका अर्थ है "ऊपर" और "नीचे" दोनों भाग। यह कोपायलट को रोलबैक माइग्रेशन भरने की अनुमति देगा। और यह तथ्य कि यह एक साधारण SQL है, इसे सबसे लचीला और समर्थित समाधान भी बनाता है।
  2. वर्तमान में लागू संस्करण को टूल द्वारा प्रबंधित किया जाना चाहिए। मैं चाहता हूं कि उपकरण आत्मनिर्भर हो।
  3. मैं चाहता हूं कि टूल विभिन्न डेटाबेस, जैसे पोस्टग्रेज, मायएसक्यूएल, एसक्यूएल सर्वर इत्यादि का समर्थन करे, इसलिए इसे उस अर्थ में विस्तार योग्य होना चाहिए।
  4. मैं नहीं चाहता कि इसका आकार बड़ा हो, इसलिए केवल आवश्यक डेटाबेस के लिए ड्राइवर स्थापित किए जाने चाहिए, आदर्श रूप से मांग पर।
  5. मैं चाहता हूं कि यह जावास्क्रिप्ट पारिस्थितिकी तंत्र का हिस्सा बने क्योंकि जिन परियोजनाओं पर मैं काम करता हूं उनमें से अधिकांश इसका हिस्सा हैं।
  6. प्रत्येक माइग्रेशन लेनदेन के अंदर किया जाना चाहिए।

परिचय

इनमें से बहुत से बिंदु टर्न नामक इस अद्भुत उपकरण के साथ मेरे अनुभव से पैदा हुए थे। मुझे दुःख था कि जावास्क्रिप्ट में वैसा नहीं है! (या शायद मुझे गूगल करना अच्छा नहीं लगता...)। इसलिए मैंने फैसला किया कि यह मेरे लिए एक अच्छा कोडिंग अभ्यास हो सकता है और एक ऐसी कहानी जो किसी और के लिए दिलचस्प हो सकती है :)

विकास

भाग 1. उपकरण को डिज़ाइन करना

आइए चोरी करें सीएलआई टूल डिज़ाइन करें!

  1. सभी माइग्रेशन में निम्नलिखित नामकरण योजना होगी: _.sql, जहां संख्या माइग्रेशन संस्करण संख्या का प्रतिनिधित्व करेगी, उदाहरण के लिए, 001_initial_setup.sql.
  2. सभी माइग्रेशन एक ही डीआईआर में रहेंगे।
  3. डेटाबेस ड्राइवर को मांग पर डाउनलोड किया जाएगा, या तो कुछ पूर्व-बंडल पैकेज या बस कुछ प्रकार के एनपीएम इंस्टॉल जारी करना।

तो टूल के लिए सिंटैक्स इस प्रकार होगा: मार्टलेट अप --डेटाबेस-यूआरएल --ड्राइवर --डीआईआर या मार्टलेट डाउन ।

जहां "अप" को उन सभी माइग्रेशन को लागू करना चाहिए जो अभी तक लागू नहीं किए गए हैं और डाउन को निर्दिष्ट संस्करण में वापस रोल करना चाहिए।
विकल्पों के निम्नलिखित अर्थ और डिफ़ॉल्ट हैं:

  • डेटाबेस-यूआरएल - डेटाबेस के लिए कनेक्शन स्ट्रिंग, डिफ़ॉल्ट रूप से एनवी वेरिएबल DATABASE_URL को देखना होगा
  • ड्राइवर - उपयोग करने के लिए डेटाबेस ड्राइवर। पहले संस्करण के लिए, मैं केवल "पीजी" नामक विकल्प के साथ पोस्टग्रेज का समर्थन करूंगा।
  • dir - निर्देशिका जहां माइग्रेशन रहता है, डिफ़ॉल्ट माइग्रेशन है

जैसा कि आप देख सकते हैं, मैंने यह पता लगाना शुरू कर दिया है कि कोई भी वास्तविक कोड लिखने से पहले मैं टूल को कैसे लागू करूंगा। यह एक अच्छा अभ्यास है, यह आवश्यकताओं को समझने और विकास चक्र को कम करने में मदद करता है।

भाग 2. कार्यान्वयन

2.1 पार्सिंग विकल्प

ठीक है, सबसे पहले चीज़ें! आइए एक Index.js फ़ाइल बनाएं और सहायता संदेश आउटपुट करें। यह कुछ इस तरह दिखेगा:

function printHelp() {
  console.log(
    "Usage: martlet up --driver  --dir  --database-url ",
  );
  console.log(
    "       martlet down  --driver  --dir  --database-url ",
  );
  console.log(
    "        is a number that specifies the version to migrate down to",
  );
  console.log("Options:");
  console.log('  --driver   Driver to use, default is "pg"');
  console.log('  --dir         Directory to use, default is "migrations"');
  console.log(
    "  --database-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 



जैसा कि आप देख सकते हैं, मैं पार्सिंग के लिए किसी लाइब्रेरी का उपयोग नहीं करता; मैं बस तर्क सूची को दोहराता हूं और हर विकल्प को संसाधित करता हूं। इसलिए, यदि मेरे पास एक बूलियन विकल्प है, तो मैं पुनरावृत्ति सूचकांक को 1 से स्थानांतरित कर दूंगा, और यदि मेरे पास एक मूल्य वाला विकल्प है, तो मैं इसे 2 से स्थानांतरित कर दूंगा।

2.2 ड्राइवर एडॉप्टर का कार्यान्वयन

कई ड्राइवरों का समर्थन करने के लिए, हमें डेटाबेस तक पहुंचने के लिए कुछ सार्वभौमिक इंटरफ़ेस की आवश्यकता है; यह इस प्रकार दिख सकता है:

interface Adapter {
    connect(url: string): Promise;
    transact(query: (fn: (text) => Promise)): Promise;
    close(): Promise;
}

मुझे लगता है कि कनेक्ट और क्लोज़ बहुत स्पष्ट कार्य हैं, मैं लेनदेन विधि समझाता हूं। इसे एक ऐसे फ़ंक्शन को स्वीकार करना चाहिए जिसे ऐसे फ़ंक्शन के साथ कॉल किया जाएगा जो एक क्वेरी टेक्स्ट स्वीकार करता है और एक मध्यवर्ती परिणाम के साथ एक वादा लौटाता है। इस जटिलता के लिए एक सामान्य इंटरफ़ेस की आवश्यकता होती है जो लेनदेन के अंदर कई प्रश्नों को चलाने की क्षमता प्रदान करेगा। उपयोग के उदाहरण को देखकर इसे समझना आसान है।

तो पोस्टग्रेज ड्राइवर के लिए एडॉप्टर इस प्रकार दिखता है:

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: "[email protected]",
};

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",
  });
};

हम पहले यार्न के साथ ड्राइवर को स्थापित करने का प्रयास करते हैं, लेकिन हम निर्देशिका में कोई अंतर उत्पन्न नहीं करना चाहते हैं, इसलिए हम यार्न.लॉक और पैकेज.जेसन फ़ाइलों को संरक्षित करते हैं। यदि यार्न उपलब्ध नहीं है, तो हम एनपीएम पर वापस आ जाएंगे।

जब हमने यह सुनिश्चित कर लिया कि ड्राइवर स्थापित है, तो हम एक एडाप्टर बना सकते हैं और उसका उपयोग कर सकते हैं:

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}`);

फिर, हम माइग्रेशन निर्देशिका को पढ़ते हैं और उन्हें संस्करण के अनुसार क्रमबद्ध करते हैं। उसके बाद, हम प्रत्येक माइग्रेशन को लागू करते हैं जिसका संस्करण वर्तमान से बड़ा है। मैं केवल निम्नलिखित स्निपेट में वास्तविक माइग्रेशन प्रस्तुत करूंगा:

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}`);
});

रोलबैक माइग्रेशन समान है, लेकिन हम माइग्रेशन को उल्टे क्रम में क्रमबद्ध करते हैं और वांछित संस्करण तक पहुंचने तक उन्हें लागू करते हैं।

3. परीक्षण

मैंने किसी विशिष्ट परीक्षण ढांचे का उपयोग नहीं करने बल्कि अंतर्निहित नोडज परीक्षण क्षमताओं का उपयोग करने का निर्णय लिया। उनमें परीक्षण धावक और अभिकथन पैकेज शामिल हैं।

import { it, before, after, describe } from "node:test";
import assert from "node:assert";

और परीक्षण निष्पादित करने के लिए मैं node --test --test-concurrency=1 चलाऊंगा।

दरअसल, मैं कोड को एक तरह से टीडीडी तरीके से लिख रहा था। मैंने सत्यापित नहीं किया कि मेरा माइग्रेशन कोड हाथ से काम करता है, लेकिन मैं इसे परीक्षणों के साथ लिख रहा था। इसीलिए मैंने निर्णय लिया कि एंड-टू-एंड परीक्षण इस टूल के लिए सबसे उपयुक्त होंगे।
इस तरह के दृष्टिकोण के लिए, परीक्षणों को एक खाली डेटाबेस को बूटस्ट्रैप करना होगा, कुछ माइग्रेशन लागू करना होगा, जांचना होगा कि डेटाबेस सामग्री सही है, और फिर प्रारंभिक स्थिति में वापस रोल करें और पुष्टि करें कि डेटाबेस खाली है।
डेटाबेस चलाने के लिए, मैंने "टेस्टकंटेनर्स" लाइब्रेरी का उपयोग किया, जो डॉकटर के चारों ओर एक अच्छा आवरण प्रदान करता है।

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();
});

मैंने कुछ सरल माइग्रेशन लिखे और परीक्षण किया कि उन्होंने अपेक्षा के अनुरूप काम किया। यहां डेटाबेस स्थिति सत्यापन का एक उदाहरण दिया गया है:

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. निष्कर्ष

यह इस बात का उदाहरण था कि मैं जावास्क्रिप्ट पारिस्थितिकी तंत्र में एक सरल सीएलआई उपकरण के विकास को कैसे अपनाऊंगा। मैं यह नोट करना चाहता हूं कि आधुनिक जावास्क्रिप्ट पारिस्थितिकी तंत्र काफी चार्ज और शक्तिशाली है, और मैं न्यूनतम बाहरी निर्भरता के साथ टूल को लागू करने में कामयाब रहा। मैंने एक पोस्टग्रेज़ ड्राइवर का उपयोग किया जिसे मांग पर डाउनलोड किया जाएगा और परीक्षणों के लिए टेस्टकंटेनर्स का उपयोग किया जाएगा। मुझे लगता है कि यह दृष्टिकोण डेवलपर्स को एप्लिकेशन पर सबसे अधिक लचीलापन और नियंत्रण प्रदान करता है।

5. सन्दर्भ

  • मार्टलेट रेपो
  • टर्न
  • पोस्टग्रेस ड्राइवर
विज्ञप्ति वक्तव्य यह लेख यहां पुन: प्रस्तुत किया गया है: https://dev.to/duskpoet/coding-exercise-database-migration-tool-in-nodejs-30pg?1 यदि कोई उल्लंघन है, तो कृपया इसे हटाने के लिए स्टडी_गोलंग@163.com से संपर्क करें।
नवीनतम ट्यूटोरियल अधिक>

चीनी भाषा का अध्ययन करें

अस्वीकरण: उपलब्ध कराए गए सभी संसाधन आंशिक रूप से इंटरनेट से हैं। यदि आपके कॉपीराइट या अन्य अधिकारों और हितों का कोई उल्लंघन होता है, तो कृपया विस्तृत कारण बताएं और कॉपीराइट या अधिकारों और हितों का प्रमाण प्रदान करें और फिर इसे ईमेल पर भेजें: [email protected] हम इसे आपके लिए यथाशीघ्र संभालेंगे।

Copyright© 2022 湘ICP备2022001581号-3