आपका कोड एक किला है: Hexagonal Architecture कैसे बचाएगा आपका JavaScript प्रोजेक्ट
\n\nक्या आपने कभी ऐसी स्थिति का सामना किया है जहाँ डेटाबेस बदलना या बाहरी API को रिप्लेस करना आधे प्रोजेक्ट को फिर से लिखने जैसा लगा? या फिर आपने यूनिट टेस्ट लिखे जो इंटीग्रेशन टेस्ट जैसे लगते थे, क्योंकि बिज़नेस लॉजिक Express या MongoDB से बुरी तरह जुड़ा हुआ था? अगर हाँ, तो Hexagonal Architecture (Ports and Adapters) की दुनिया में आपका स्वागत है।
\n\nयह सिर्फ Alistair Cockburn की किताबों का एक फैशनेबल पैटर्न नहीं है। यह आपके JavaScript कोड को मजबूत, टेस्टेबल और फ्रेमवर्क से स्वतंत्र बनाने का एक व्यावहारिक तरीका है। इस लेख में हम उबाऊ लेक्चर नहीं पढ़ेंगे। हम एक वास्तविक समस्या (ऑर्डर मैनेजमेंट सिस्टम) लेंगे और इसे JavaScript में हेक्सागोनल आर्किटेक्चर के सिद्धांतों पर कदम दर कदम बनाएंगे। आप देखेंगे कि कैसे बिज़नेस लॉजिक बिल्कुल साफ रहता है, और सारा "शोर" (डेटाबेस, HTTP रिक्वेस्ट, क्यू) बाहर रहता है।
\n\nतैयार हैं? चलिए शुरू करते हैं!
\n\nHexagonal Architecture क्या है (और इसमें Ports और Adapters कहाँ आते हैं)?
\n\nएक अंतरिक्ष यान के गेटवे की कल्पना करें। अंदर एक कंट्रोल पैनल है (आपका बिज़नेस लॉजिक)। बाहर अनगिनत अलग-अलग ग्रह हैं (डेटाबेस, वेब फ्रेमवर्क, बाहरी सर्विसेज)। गेटवे एक पोर्ट (इंटरफ़ेस) है। और एक स्पेससूट जो किसी विशेष ग्रह पर सांस लेने में मदद करता है, वह एडॉप्टर है।
\n\nमुख्य विचार: एप्लीकेशन का कोर (core) को यह नहीं जानना चाहिए कि उस पर कौन और कैसे दस्तक दे रहा है। यह केवल "पोर्ट्स" (इंटरफ़ेस) घोषित करता है। और ठोस इम्प्लीमेंटेशन (एडॉप्टर) बाहर से जुड़ते हैं। यह तीन सुपर-पावर देता है:
\n\n- \n
- बिज़नेस लॉजिक का अलगाव — आप डेटाबेस बदल सकते हैं, डिस्काउंट कैलकुलेशन कोड को छुए बिना। \n
- टेस्टेबिलिटी — आप Docker या रियल DB चलाए बिना लॉजिक का टेस्ट कर सकते हैं। \n
- फ्रेमवर्क से स्वतंत्रता — आप Express से Fastify पर जा सकते हैं, बस एक नया एडॉप्टर लिखकर। \n
चलिए कोड की ओर बढ़ते हैं।
\n\nप्रोजेक्ट सेटअप और फोल्डर स्ट्रक्चर
\n\nपहले एक सरल स्ट्रक्चर बनाते हैं। हम शुद्ध TypeScript (या JSDoc के साथ JavaScript — जो भी आपको सुविधाजनक लगे) में लिखेंगे। हमारा लक्ष्य एक ऑर्डर मैनेजमेंट सिस्टम है।
\n\nsrc/\n├── core/\n│ ├── domain/ # Entities और Value Objects\n│ ├── ports/ # Interfaces (ports)\n│ └── services/ # Business logic (use cases)\n├── adapters/\n│ ├── primary/ # Adapters जो हमारे कोर को कॉल करते हैं (HTTP, CLI)\n│ └── secondary/ # Adapters जिन्हें कोर कॉल करता है (DB, external APIs)\n└── app.js # Entry point (सब कुछ एक साथ जोड़ना)\n\n\nचरण 1: कोर: डोमेन एंटिटीज़ और पोर्ट्स
\n\nसबसे महत्वपूर्ण चीज़ से शुरू करते हैं — जो किसी पर निर्भर नहीं है। एक Order एंटिटी बनाते हैं।
core/domain/Order.js
\n\nexport class Order {\n constructor({ id, userId, items, status = 'pending' }) {\n this.id = id;\n this.userId = userId;\n this.items = items; // Array { productId, quantity, price }\n this.status = status;\n this.total = this.calculateTotal();\n }\n\n calculateTotal() {\n return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }\n\n approve() {\n if (this.status !== 'pending') {\n throw new Error('Only pending orders can be approved');\n }\n this.status = 'approved';\n }\n}\n\n\nअब पोर्ट्स को परिभाषित करते हैं — वे इंटरफ़ेस जिनकी हमारा कोर बाहरी दुनिया से अपेक्षा करता है।
\n\ncore/ports/OrderRepository.js
\n\n// यह एक पोर्ट (इंटरफ़ेस) है। हम इसे यहाँ इम्प्लीमेंट नहीं करते।\nexport class OrderRepository {\n async save(order) { throw new Error('Not implemented'); }\n async findById(id) { throw new Error('Not implemented'); }\n}\n\n\ncore/ports/EmailService.js
\n\nexport class EmailService {\n async sendOrderConfirmation(order) { throw new Err