मैं एक डेटाबेस माइग्रेशन टूल चाहता हूं, जिसमें निम्नलिखित गुण हों:
इनमें से बहुत से बिंदु टर्न नामक इस अद्भुत उपकरण के साथ मेरे अनुभव से पैदा हुए थे। मुझे दुःख था कि जावास्क्रिप्ट में वैसा नहीं है! (या शायद मुझे गूगल करना अच्छा नहीं लगता...)। इसलिए मैंने फैसला किया कि यह मेरे लिए एक अच्छा कोडिंग अभ्यास हो सकता है और एक ऐसी कहानी जो किसी और के लिए दिलचस्प हो सकती है :)
आइए चोरी करें सीएलआई टूल डिज़ाइन करें!
तो टूल के लिए सिंटैक्स इस प्रकार होगा: मार्टलेट अप --डेटाबेस-यूआरएल --ड्राइवर --डीआईआर या मार्टलेट डाउन ।
जहां "अप" को उन सभी माइग्रेशन को लागू करना चाहिए जो अभी तक लागू नहीं किए गए हैं और डाउन को निर्दिष्ट संस्करण में वापस रोल करना चाहिए।
विकल्पों के निम्नलिखित अर्थ और डिफ़ॉल्ट हैं:
जैसा कि आप देख सकते हैं, मैंने यह पता लगाना शुरू कर दिया है कि कोई भी वास्तविक कोड लिखने से पहले मैं टूल को कैसे लागू करूंगा। यह एक अच्छा अभ्यास है, यह आवश्यकताओं को समझने और विकास चक्र को कम करने में मदद करता है।
ठीक है, सबसे पहले चीज़ें! आइए एक 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. सन्दर्भ
अस्वीकरण: उपलब्ध कराए गए सभी संसाधन आंशिक रूप से इंटरनेट से हैं। यदि आपके कॉपीराइट या अन्य अधिकारों और हितों का कोई उल्लंघन होता है, तो कृपया विस्तृत कारण बताएं और कॉपीराइट या अधिकारों और हितों का प्रमाण प्रदान करें और फिर इसे ईमेल पर भेजें: [email protected] हम इसे आपके लिए यथाशीघ्र संभालेंगे।
Copyright© 2022 湘ICP备2022001581号-3