जावास्क्रिप्ट में डोमेन-संचालित डिज़ाइन में महारत हासिल करें। मजबूत डोमेन ऑब्जेक्ट मॉडल के साथ स्केलेबल, परीक्षण योग्य और रखरखाव योग्य एप्लिकेशन बनाने के लिए मॉड्यूल एंटिटी पैटर्न सीखें।
जावास्क्रिप्ट मॉड्यूल एंटिटी पैटर्न: डोमेन ऑब्जेक्ट मॉडलिंग में एक गहन विश्लेषण
सॉफ़्टवेयर डेवलपमेंट की दुनिया में, विशेष रूप से गतिशील और लगातार विकसित हो रहे जावास्क्रिप्ट इकोसिस्टम के भीतर, हम अक्सर गति, फ़्रेमवर्क और सुविधाओं को प्राथमिकता देते हैं। हम जटिल यूज़र इंटरफ़ेस बनाते हैं, अनगिनत एपीआई से जुड़ते हैं, और तेज़ी से एप्लिकेशन डिप्लॉय करते हैं। लेकिन इस हड़बड़ी में, हम कभी-कभी अपने एप्लिकेशन के मूल को अनदेखा कर देते हैं: व्यावसायिक डोमेन। यह अक्सर "बिग बॉल ऑफ़ मड" नामक स्थिति का कारण बन सकता है—एक ऐसा सिस्टम जहाँ व्यावसायिक तर्क बिखरा हुआ होता है, डेटा असंरचित होता है, और एक साधारण बदलाव अप्रत्याशित बगों की एक श्रृंखला को ट्रिगर कर सकता है।
यहीं पर डोमेन ऑब्जेक्ट मॉडलिंग काम आता है। यह उस समस्या क्षेत्र का एक समृद्ध, अभिव्यंजक मॉडल बनाने का अभ्यास है जिसमें आप काम कर रहे हैं। और जावास्क्रिप्ट में, मॉड्यूल एंटिटी पैटर्न इसे प्राप्त करने का एक शक्तिशाली, सुरुचिपूर्ण और फ़्रेमवर्क-अज्ञेयवादी तरीका है। यह व्यापक मार्गदर्शिका आपको इस पैटर्न के सिद्धांत, अभ्यास और लाभों के माध्यम से ले जाएगी, जिससे आप अधिक मजबूत, स्केलेबल और रखरखाव योग्य एप्लिकेशन बनाने में सक्षम होंगे।
डोमेन ऑब्जेक्ट मॉडलिंग क्या है?
पैटर्न में गहराई से जाने से पहले, आइए हम अपनी शब्दावली को स्पष्ट करें। इस अवधारणा को ब्राउज़र के डॉक्यूमेंट ऑब्जेक्ट मॉडल (DOM) से अलग करना महत्वपूर्ण है।
- डोमेन: सॉफ्टवेयर में, 'डोमेन' विशिष्ट विषय क्षेत्र है जिससे उपयोगकर्ता का व्यवसाय संबंधित है। एक ई-कॉमर्स एप्लिकेशन के लिए, डोमेन में उत्पाद, ग्राहक, ऑर्डर और भुगतान जैसी अवधारणाएं शामिल हैं। एक सोशल मीडिया प्लेटफॉर्म के लिए, इसमें उपयोगकर्ता, पोस्ट, टिप्पणियां और लाइक शामिल हैं।
- डोमेन ऑब्जेक्ट मॉडलिंग: यह एक सॉफ्टवेयर मॉडल बनाने की प्रक्रिया है जो उस व्यावसायिक डोमेन के भीतर संस्थाओं, उनके व्यवहार और उनके संबंधों का प्रतिनिधित्व करता है। यह वास्तविक दुनिया की अवधारणाओं को कोड में अनुवाद करने के बारे में है।
एक अच्छा डोमेन मॉडल केवल डेटा कंटेनरों का एक संग्रह नहीं है। यह आपके व्यावसायिक नियमों का एक जीवंत प्रतिनिधित्व है। एक ऑर्डर ऑब्जेक्ट को केवल आइटम की सूची नहीं रखनी चाहिए; उसे यह जानना चाहिए कि उसका कुल कैसे गणना करें, एक नया आइटम कैसे जोड़ें, और क्या इसे रद्द किया जा सकता है। डेटा और व्यवहार का यह एनकैप्सुलेशन एक लचीला एप्लिकेशन कोर बनाने की कुंजी है।
आम समस्या: "मॉडल" लेयर में अराजकता
कई जावास्क्रिप्ट एप्लिकेशनों में, विशेष रूप से वे जो स्वाभाविक रूप से बढ़ते हैं, 'मॉडल' लेयर अक्सर बाद में सोची जाती है। हम अक्सर इस एंटी-पैटर्न को देखते हैं:
// Somewhere in an API controller or service...
async function createUser(req, res) {
const { email, password, firstName, lastName } = req.body;
// Business logic and validation is scattered here
if (!email || !email.includes('@')) {
return res.status(400).send({ error: 'A valid email is required.' });
}
if (!password || password.length < 8) {
return res.status(400).send({ error: 'Password must be at least 8 characters.' });
}
const user = {
email: email.toLowerCase(),
password: await hashPassword(password), // Some utility function
fullName: `${firstName} ${lastName}`, // Logic for derived data is here
createdAt: new Date()
};
// Now, what is `user`? It's just a plain object.
// Nothing stops another developer from doing this later:
// user.email = 'an-invalid-email';
// user.password = 'short';
await db.users.insert(user);
res.status(201).send(user);
}
यह दृष्टिकोण कई महत्वपूर्ण समस्याएँ प्रस्तुत करता है:
- सत्य का कोई एकल स्रोत नहीं: एक वैध 'उपयोगकर्ता' क्या बनाता है, इसके नियम इस एक कंट्रोलर के अंदर परिभाषित किए गए हैं। यदि सिस्टम के किसी अन्य हिस्से को उपयोगकर्ता बनाने की आवश्यकता है तो क्या होगा? क्या आप लॉजिक को कॉपी-पेस्ट करेंगे? इससे असंगति और बग उत्पन्न होते हैं।
- एनीमिक डोमेन मॉडल: द `यूज़र` ऑब्जेक्ट केवल डेटा का एक 'बेकार' संग्रह है। इसमें कोई व्यवहार या आत्म-जागरूकता नहीं है। इस पर काम करने वाला सारा तर्क बाहरी रूप से रहता है।
- निम्न सामंजस्य: उपयोगकर्ता का पूरा नाम बनाने का तर्क एपीआई अनुरोध/प्रतिक्रिया हैंडलिंग और पासवर्ड हैशिंग के साथ मिलाया गया है।
- परीक्षण करना मुश्किल: उपयोगकर्ता निर्माण तर्क का परीक्षण करने के लिए, आपको HTTP अनुरोधों और प्रतिक्रियाओं, डेटाबेस और हैशिंग फ़ंक्शंस का मॉक करना होगा। आप 'उपयोगकर्ता' अवधारणा का अकेले परीक्षण नहीं कर सकते।
- निहित अनुबंध: एप्लिकेशन के शेष भाग को केवल यह 'मानना' पड़ता है कि उपयोगकर्ता का प्रतिनिधित्व करने वाले किसी भी ऑब्जेक्ट का एक निश्चित आकार है और उसका डेटा वैध है। कोई गारंटी नहीं है।
समाधान: जावास्क्रिप्ट मॉड्यूल एंटिटी पैटर्न
मॉड्यूल एंटिटी पैटर्न इन समस्याओं को एक मानक जावास्क्रिप्ट मॉड्यूल (एक फ़ाइल) का उपयोग करके संबोधित करता है ताकि एक ही डोमेन अवधारणा के बारे में सब कुछ परिभाषित किया जा सके। यह मॉड्यूल उस एंटिटी के लिए सत्य का निश्चित स्रोत बन जाता है।
एक मॉड्यूल एंटिटी आमतौर पर एक फ़ैक्टरी फ़ंक्शन को उजागर करता है। यह फ़ंक्शन एंटिटी का एक वैध इंस्टेंस बनाने के लिए जिम्मेदार है। यह जो ऑब्जेक्ट लौटाता है वह केवल डेटा नहीं है; यह एक समृद्ध डोमेन ऑब्जेक्ट है जो अपने स्वयं के डेटा, सत्यापन और व्यावसायिक तर्क को एनकैप्सुलेट करता है।
एक मॉड्यूल एंटिटी की मुख्य विशेषताएं
- एनकैप्सुलेशन: यह डेटा और उस डेटा पर काम करने वाले फ़ंक्शन को एक साथ बंडल करता है।
- सीमा पर सत्यापन: यह सुनिश्चित करता है कि एक अमान्य एंटिटी बनाना असंभव है। यह अपनी स्थिति की रक्षा करता है।
- स्पष्ट एपीआई: यह एंटिटी के साथ इंटरैक्ट करने के लिए फ़ंक्शंस (एक सार्वजनिक एपीआई) का एक स्वच्छ, उद्देश्यपूर्ण सेट उजागर करता है, जबकि आंतरिक कार्यान्वयन विवरणों को छिपाता है।
- अपरिवर्तनीयता (Immutability): यह अक्सर आकस्मिक स्थिति परिवर्तनों को रोकने और अनुमानित व्यवहार सुनिश्चित करने के लिए अपरिवर्तनीय या केवल-पढ़ने योग्य ऑब्जेक्ट बनाता है।
- पोर्टेबिलिटी: इसमें फ़्रेमवर्क (जैसे एक्सप्रेस, रिएक्ट) या बाहरी सिस्टम (जैसे डेटाबेस, एपीआई) पर कोई निर्भरता नहीं है। यह शुद्ध व्यावसायिक तर्क है।
एक मॉड्यूल एंटिटी के मुख्य घटक
आइए इस पैटर्न का उपयोग करके अपनी `यूज़र` अवधारणा को फिर से बनाएँ। हम एक फ़ाइल, `user.js` (या टाइपस्क्रिप्ट उपयोगकर्ताओं के लिए `user.ts`) बनाएंगे, और इसे चरण-दर-चरण बनाएंगे।
1. फ़ैक्टरी फ़ंक्शन: आपका ऑब्जेक्ट कंस्ट्रक्टर
क्लास के बजाय, हम एक फ़ैक्टरी फ़ंक्शन (जैसे, `buildUser`) का उपयोग करेंगे। फ़ैक्टरी बहुत लचीलापन प्रदान करती हैं, `this` कीवर्ड के साथ संघर्ष से बचती हैं, और जावास्क्रिप्ट में निजी स्थिति और एनकैप्सुलेशन को अधिक स्वाभाविक बनाती हैं।
हमारा लक्ष्य एक ऐसा फ़ंक्शन बनाना है जो कच्चा डेटा लेता है और एक सुव्यवस्थित, विश्वसनीय उपयोगकर्ता ऑब्जेक्ट लौटाता है।
// file: /domain/user.js
export default function buildMakeUser() {
// This inner function is the actual factory.
// It has access to any dependencies passed to buildMakeUser, if needed.
return function makeUser({
id = generateId(), // Let's assume a function to generate a unique ID
firstName,
lastName,
email,
passwordHash,
createdAt = new Date()
}) {
// ... validation and logic will go here ...
const user = {
getId: () => id,
getFirstName: () => firstName,
getLastName: () => lastName,
getEmail: () => email,
getPasswordHash: () => passwordHash,
getCreatedAt: () => createdAt
};
// Using Object.freeze to make the object immutable.
return Object.freeze(user);
}
}
यहां कुछ बातों पर ध्यान दें। हम एक फ़ंक्शन का उपयोग कर रहे हैं जो एक फ़ंक्शन (एक हायर-ऑर्डर फ़ंक्शन) लौटाता है। यह डिपेंडेंसीज़ को इंजेक्ट करने का एक शक्तिशाली पैटर्न है, जैसे एक अद्वितीय आईडी जेनरेटर या एक वैलिडेटर लाइब्रेरी, बिना एंटिटी को एक विशिष्ट कार्यान्वयन से जोड़े। अभी के लिए, हम इसे सरल रखेंगे।
2. डेटा सत्यापन: द्वार पर संरक्षक
एक एंटिटी को अपनी अखंडता की रक्षा करनी चाहिए। अमान्य स्थिति में एक `यूज़र` बनाना असंभव होना चाहिए। हम फ़ैक्टरी फ़ंक्शन के अंदर ही सत्यापन जोड़ते हैं। यदि डेटा अमान्य है, तो फ़ैक्टरी को एक त्रुटि फेंकनी चाहिए, स्पष्ट रूप से यह बताते हुए कि क्या गलत है।
// file: /domain/user.js
export default function buildMakeUser({ Id, isValidEmail, hashPassword }) {
return function makeUser({
id = Id.makeId(),
firstName,
lastName,
email,
password, // We now take a plain password and handle it inside
createdAt = new Date()
}) {
if (!Id.isValidId(id)) {
throw new Error('User must have a valid id.');
}
if (!firstName || firstName.length < 2) {
throw new Error('First name must be at least 2 characters long.');
}
if (!lastName || lastName.length < 2) {
throw new Error('Last name must be at least 2 characters long.');
}
if (!email || !isValidEmail(email)) {
throw new Error('User must have a valid email address.');
}
if (!password || password.length < 8) {
throw new Error('Password must be at least 8 characters long.');
}
// Data normalization and transformation happens here
const passwordHash = hashPassword(password);
const normalizedEmail = email.toLowerCase();
return Object.freeze({
getId: () => id,
getFirstName: () => firstName,
getLastName: () => lastName,
getEmail: () => normalizedEmail,
getPasswordHash: () => passwordHash,
getCreatedAt: () => createdAt
});
}
}
अब, हमारे सिस्टम का कोई भी हिस्सा जो एक `यूज़र` बनाना चाहता है, उसे इस फ़ैक्टरी के माध्यम से जाना होगा। हमें हर बार गारंटीकृत सत्यापन मिलता है। हमने पासवर्ड को हैश करने और ईमेल पते को सामान्य बनाने के तर्क को भी एनकैप्सुलेट किया है। एप्लिकेशन के बाकी हिस्सों को इन विवरणों के बारे में जानने या परवाह करने की आवश्यकता नहीं है।
3. व्यावसायिक तर्क: व्यवहार को एनकैप्सुलेट करना
हमारा `यूज़र` ऑब्जेक्ट अभी भी थोड़ा एनीमिक है। इसमें डेटा है, लेकिन यह कुछ भी *नहीं करता* है। आइए हम व्यवहार जोड़ें—ऐसे तरीके जो डोमेन-विशिष्ट कार्यों का प्रतिनिधित्व करते हैं।
// ... inside the makeUser function ...
if (!password || password.length < 8) {
// ...
}
const passwordHash = hashPassword(password);
const normalizedEmail = email.toLowerCase();
return Object.freeze({
getId: () => id,
getFirstName: () => firstName,
getLastName: () => lastName,
getEmail: () => normalizedEmail,
getPasswordHash: () => passwordHash,
getCreatedAt: () => createdAt,
// Business Logic / Behavior
getFullName: () => `${firstName} ${lastName}`,
// A method that describes a business rule
canVote: () => {
// In some countries, voting age is 18. This is a business rule.
// Let's assume we have a dateOfBirth property.
const age = calculateAge(dateOfBirth);
return age >= 18;
}
});
// ...
`getFullName` लॉजिक अब किसी रैंडम कंट्रोलर में बिखरा हुआ नहीं है; यह `यूज़र` एंटिटी से संबंधित है। `यूज़र` ऑब्जेक्ट वाला कोई भी व्यक्ति अब `user.getFullName()` को कॉल करके मज़बूती से पूरा नाम प्राप्त कर सकता है। लॉजिक एक बार, एक ही जगह पर परिभाषित किया गया है।
एक व्यावहारिक उदाहरण बनाना: एक साधारण ई-कॉमर्स सिस्टम
आइए इस पैटर्न को एक अधिक इंटरकनेक्टेड डोमेन पर लागू करें। हम एक `प्रोडक्ट`, एक `ऑर्डर आइटम`, और एक `ऑर्डर` को मॉडल करेंगे।
1. `प्रोडक्ट` एंटिटी का मॉडलिंग करना
एक उत्पाद का एक नाम, एक कीमत और कुछ स्टॉक जानकारी होती है। इसका एक नाम होना चाहिए, और इसकी कीमत नकारात्मक नहीं हो सकती है।
// file: /domain/product.js
export default function buildMakeProduct({ Id }) {
return function makeProduct({
id = Id.makeId(),
name,
description,
price,
stock = 0
}) {
if (!Id.isValidId(id)) {
throw new Error('Product must have a valid ID.');
}
if (!name || name.trim().length < 2) {
throw new Error('Product name must be at least 2 characters.');
}
if (isNaN(price) || price <= 0) {
throw new Error('Product must have a price greater than zero.');
}
if (isNaN(stock) || stock < 0) {
throw new Error('Stock must be a non-negative number.');
}
return Object.freeze({
getId: () => id,
getName: () => name,
getDescription: () => description,
getPrice: () => price,
getStock: () => stock,
// Business logic
isAvailable: () => stock > 0,
// A method that modifies state by returning a new instance
reduceStock: (amount) => {
if (amount > stock) {
throw new Error('Not enough stock available.');
}
// Return a NEW product object with the updated stock
return makeProduct({ id, name, description, price, stock: stock - amount });
}
});
}
}
`reduceStock` विधि पर ध्यान दें। यह अपरिवर्तनीयता से संबंधित एक महत्वपूर्ण अवधारणा है। मौजूदा ऑब्जेक्ट पर `stock` प्रॉपर्टी को बदलने के बजाय, यह अपडेटेड मान के साथ एक *नया* `प्रोडक्ट` इंस्टेंस लौटाता है। यह स्थिति परिवर्तनों को स्पष्ट और अनुमानित बनाता है।
2. `ऑर्डर` एंटिटी का मॉडलिंग करना (एग्रीगेट रूट)
एक `ऑर्डर` अधिक जटिल होता है। डोमेन-ड्रिवन डिज़ाइन (DDD) इसे "एग्रीगेट रूट" कहता है। यह एक एंटिटी है जो अपनी सीमा के भीतर अन्य, छोटे ऑब्जेक्ट्स का प्रबंधन करती है। एक `ऑर्डर` में `ऑर्डर आइटम` की सूची होती है। आप सीधे ऑर्डर में कोई उत्पाद नहीं जोड़ते हैं; आप एक `ऑर्डर आइटम` जोड़ते हैं जिसमें एक उत्पाद और एक मात्रा होती है।
// file: /domain/order.js
export const ORDER_STATUS = {
PENDING: 'PENDING',
PAID: 'PAID',
SHIPPED: 'SHIPPED',
CANCELLED: 'CANCELLED'
};
export default function buildMakeOrder({ Id, validateOrderItem }) {
return function makeOrder({
id = Id.makeId(),
customerId,
items = [],
status = ORDER_STATUS.PENDING,
createdAt = new Date()
}) {
if (!Id.isValidId(id)) {
throw new Error('Order must have a valid ID.');
}
if (!customerId) {
throw new Error('Order must have a customer ID.');
}
let orderItems = [...items]; // Create a private copy to manage
return Object.freeze({
getId: () => id,
getCustomerId: () => customerId,
getItems: () => [...orderItems], // Return a copy to prevent external modification
getStatus: () => status,
getCreatedAt: () => createdAt,
// Business Logic
calculateTotal: () => {
return orderItems.reduce((total, item) => {
return total + (item.getPrice() * item.getQuantity());
}, 0);
},
addItem: (item) => {
// validateOrderItem is a function that ensures the item is a valid OrderItem entity
validateOrderItem(item);
// Business rule: prevent adding duplicates, just increase quantity
const existingItemIndex = orderItems.findIndex(i => i.getProductId() === item.getProductId());
if (existingItemIndex > -1) {
const newQuantity = orderItems[existingItemIndex].getQuantity() + item.getQuantity();
// Here you'd update the quantity on the existing item
// (This requires items to be mutable or have an update method)
} else {
orderItems.push(item);
}
},
markPaid: () => {
if (status !== ORDER_STATUS.PENDING) {
throw new Error('Only pending orders can be marked as paid.');
}
// Return a new Order instance with the updated status
return makeOrder({ id, customerId, items: orderItems, status: ORDER_STATUS.PAID, createdAt });
}
});
}
}
यह `ऑर्डर` एंटिटी अब जटिल व्यावसायिक नियमों को लागू करती है:
- यह आइटम की अपनी सूची का प्रबंधन करता है।
- यह अपने कुल की गणना करना जानता है।
- यह स्थिति परिवर्तनों को लागू करता है (उदाहरण के लिए, आप केवल एक `लंबित` ऑर्डर को `भुगतान किया गया` के रूप में चिह्नित कर सकते हैं)।
ऑर्डर के लिए व्यावसायिक तर्क अब इस मॉड्यूल के भीतर अच्छी तरह से एनकैप्सुलेटेड है, अलगाव में परीक्षण योग्य है, और आपके पूरे एप्लिकेशन में पुन: प्रयोज्य है।
उन्नत पैटर्न और विचार
अपरिवर्तनीयता (Immutability): अनुमानितता का आधार
हमने अपरिवर्तनीयता पर बात की है। यह इतनी महत्वपूर्ण क्यों है? जब ऑब्जेक्ट अपरिवर्तनीय होते हैं, तो आप उन्हें अपने एप्लिकेशन में इस डर के बिना पास कर सकते हैं कि कोई दूर का फ़ंक्शन अप्रत्याशित रूप से उनकी स्थिति बदल देगा। यह बग्स के एक पूरे वर्ग को समाप्त करता है और आपके एप्लिकेशन के डेटा प्रवाह को समझना बहुत आसान बनाता है।
Object.freeze() एक शैलो फ़्रीज़ प्रदान करता है। नेस्टेड ऑब्जेक्ट्स या एरेज़ (जैसे हमारा `ऑर्डर`) वाली एंटिटीज़ के लिए, आपको अधिक सावधान रहने की आवश्यकता है। उदाहरण के लिए, `order.getItems()` में, हमने कॉलर को सीधे ऑर्डर के आंतरिक एरे में आइटम पुश करने से रोकने के लिए एक कॉपी (`[...orderItems]`) लौटाई।
जटिल एप्लिकेशनों के लिए, Immer जैसी लाइब्रेरी अपरिवर्तनीय नेस्टेड संरचनाओं के साथ काम करना बहुत आसान बना सकती हैं, लेकिन मुख्य सिद्धांत वही रहता है: अपनी एंटिटीज़ को अपरिवर्तनीय मूल्यों के रूप में मानें। जब कोई बदलाव होने की आवश्यकता हो, तो एक नया मान बनाएँ।
अतुल्यकालिक संचालन और दृढ़ता को संभालना
आपने देखा होगा कि हमारी एंटिटीज़ पूरी तरह से तुल्यकालिक हैं। वे डेटाबेस या एपीआई के बारे में कुछ भी नहीं जानते हैं। यह जानबूझकर है और पैटर्न की एक प्रमुख ताकत है!
एंटिटीज़ को खुद को सहेजना नहीं चाहिए। एक एंटिटी का काम व्यावसायिक नियमों को लागू करना है। डेटा को डेटाबेस में सहेजने का काम आपके एप्लिकेशन की एक अलग परत से संबंधित है, जिसे अक्सर सेवा परत, उपयोग केस परत, या रिपॉजिटरी पैटर्न कहा जाता है।
वे इस प्रकार इंटरैक्ट करते हैं:
// file: /use-cases/create-user.js
// This use case depends on the user entity factory and a database access function.
export default function makeCreateUser({ makeUser, usersDatabase }) {
return async function createUser(userInfo) {
// 1. Create a valid domain entity. This step validates the data.
const user = makeUser(userInfo);
// 2. Check for business rules that require external data (e.g., email uniqueness)
const exists = await usersDatabase.findByEmail({ email: user.getEmail() });
if (exists) {
throw new Error('Email address is already in use.');
}
// 3. Persist the entity. The database needs a plain object.
const persisted = await usersDatabase.insert({
id: user.getId(),
firstName: user.getFirstName(),
// ... and so on
});
return persisted;
}
}
चिंताओं का यह पृथक्करण शक्तिशाली है:
- `यूज़र` एंटिटी शुद्ध, तुल्यकालिक और इकाई परीक्षण के लिए आसान है।
- `createUser` उपयोग केस ऑर्केस्ट्रेशन के लिए जिम्मेदार है और एक मॉक डेटाबेस के साथ एकीकरण-परीक्षण किया जा सकता है।
- `usersDatabase` मॉड्यूल विशिष्ट डेटाबेस तकनीक के लिए जिम्मेदार है और इसे अलग से परीक्षण किया जा सकता है।
सीरियलाइज़ेशन और डीसीरियलाइज़ेशन
आपकी एंटिटीज़, अपनी विधियों के साथ, समृद्ध ऑब्जेक्ट हैं। लेकिन जब आप नेटवर्क पर डेटा भेजते हैं (उदाहरण के लिए, JSON API प्रतिक्रिया में) या इसे डेटाबेस में संग्रहीत करते हैं, तो आपको एक सादे डेटा प्रतिनिधित्व की आवश्यकता होती है। इस प्रक्रिया को सीरियलाइज़ेशन कहा जाता है।
एक सामान्य पैटर्न आपकी एंटिटी में एक `toJSON()` या `toObject()` विधि जोड़ना है।
// ... inside the makeUser function ...
return Object.freeze({
getId: () => id,
// ... other getters
// Serialization method
toObject: () => ({
id,
firstName,
lastName,
email: normalizedEmail,
createdAt
// Notice we don't include the passwordHash
})
});
विपरीत प्रक्रिया, डेटाबेस या एपीआई से सादा डेटा लेना और उसे वापस एक समृद्ध डोमेन एंटिटी में बदलना, ठीक वही है जिसके लिए आपका `makeUser` फ़ैक्टरी फ़ंक्शन है। यह डीसीरियलाइज़ेशन है।
टाइपस्क्रिप्ट या जेएसडॉक के साथ टाइपिंग
हालांकि यह पैटर्न वैनिला जावास्क्रिप्ट में पूरी तरह से काम करता है, टाइपस्क्रिप्ट या जेएसडॉक के साथ स्टैटिक प्रकार जोड़ने से यह सुपरचार्ज हो जाता है। प्रकार आपको अपनी एंटिटी के 'आकार' को औपचारिक रूप से परिभाषित करने की अनुमति देते हैं, जो उत्कृष्ट ऑटो-कंप्लीशन और कंपाइल-टाइम चेक प्रदान करते हैं।
// file: /domain/user.ts
// Define the entity's public interface
export type User = Readonly<{
getId: () => string;
getFirstName: () => string;
// ... etc
getFullName: () => string;
}>;
// The factory function now returns the User type
export default function buildMakeUser(...) {
return function makeUser(...): User {
// ... implementation
}
}
मॉड्यूल एंटिटी पैटर्न के समग्र लाभ
इस पैटर्न को अपनाने से, आपको कई लाभ मिलते हैं जो आपके एप्लिकेशन के बढ़ने के साथ बढ़ते जाते हैं:
- सत्य का एकल स्रोत: व्यावसायिक नियम और डेटा सत्यापन केंद्रीकृत और अस्पष्ट होते हैं। किसी नियम में बदलाव केवल एक जगह पर किया जाता है।
- उच्च सामंजस्य, निम्न युग्मन: एंटिटीज़ आत्मनिर्भर होती हैं और बाहरी सिस्टम पर निर्भर नहीं करती हैं। यह आपके कोडबेस को मॉड्यूलर और पुनर्गठित करने में आसान बनाता है।
- उत्कृष्ट परीक्षण क्षमता: आप पूरी दुनिया का मजाक उड़ाए बिना अपने सबसे महत्वपूर्ण व्यावसायिक तर्क के लिए सरल, तेज़ इकाई परीक्षण लिख सकते हैं।
- बेहतर डेवलपर अनुभव: जब किसी डेवलपर को `यूज़र` के साथ काम करने की आवश्यकता होती है, तो उनके पास उपयोग करने के लिए एक स्पष्ट, अनुमानित और स्व-दस्तावेजीकरण एपीआई होता है। सादे ऑब्जेक्ट्स के आकार का अनुमान लगाने की आवश्यकता नहीं है।
- स्केलेबिलिटी के लिए एक आधार: यह पैटर्न आपको एक स्थिर, विश्वसनीय कोर देता है। जैसे-जैसे आप अधिक सुविधाएँ, फ़्रेमवर्क या यूआई घटक जोड़ते हैं, आपका व्यावसायिक तर्क संरक्षित और सुसंगत रहता है।
निष्कर्ष: अपने एप्लिकेशन के लिए एक ठोस कोर बनाएँ
तेजी से बदलते फ़्रेमवर्क और लाइब्रेरी की दुनिया में, यह भूलना आसान है कि ये उपकरण क्षणभंगुर हैं। वे बदल जाएंगे। जो टिका रहता है वह आपके व्यावसायिक डोमेन का मूल तर्क है। इस डोमेन को ठीक से मॉडल करने में समय निवेश करना केवल एक अकादमिक अभ्यास नहीं है; यह आपके सॉफ़्टवेयर के स्वास्थ्य और दीर्घायु में आप सबसे महत्वपूर्ण दीर्घकालिक निवेशों में से एक है।
जावास्क्रिप्ट मॉड्यूल एंटिटी पैटर्न इन विचारों को लागू करने का एक सरल, शक्तिशाली और मूल तरीका प्रदान करता है। इसे किसी भारी फ़्रेमवर्क या जटिल सेटअप की आवश्यकता नहीं है। यह भाषा की मूलभूत विशेषताओं—मॉड्यूल, फ़ंक्शन और क्लोजर—का लाभ उठाता है ताकि आपको अपने एप्लिकेशन के लिए एक स्वच्छ, लचीला और समझने योग्य कोर बनाने में मदद मिल सके। अपनी अगली परियोजना में एक मुख्य एंटिटी के साथ शुरुआत करें। इसके गुणों को मॉडल करें, इसके निर्माण को सत्यापित करें, और इसे व्यवहार दें। आप एक अधिक मजबूत और पेशेवर सॉफ्टवेयर आर्किटेक्चर की दिशा में पहला कदम उठा रहे होंगे।