Rust और Hexagonal आर्किटेक्चर: ऐसा कोड लिखना जिसे बदलने में डर न लगे
\n\nहर डेवलपर ने कम से कम एक बार ऐसी स्थिति का सामना किया है जहां डेटाबेस में एक छोटा सा बदलाव या HTTP फ्रेमवर्क बदलना एक भयानक रिफैक्टरिंग में बदल गया। बिजनेस लॉजिक कार्यान्वयन विवरणों से जुड़ जाता है, और कोड "स्पेगेटी" में बदल जाता है। Rust के लिए, जो सुरक्षा और प्रदर्शन पर ध्यान केंद्रित करता है, ऐसा मिश्रण एक वास्तविक अपराध है।
\n\nइस समस्या का समाधान है Hexagonal आर्किटेक्चर (जिसे Ports and Adapters के नाम से भी जाना जाता है)। इस लेख में, हम जानेंगे कि टाइप सिस्टम और ट्रेट्स की शक्ति का उपयोग करके इस पैटर्न को Rust में कैसे लागू किया जाए। आप सीखेंगे कि डोमेन लॉजिक को बाहरी दुनिया से कैसे अलग किया जाए, परीक्षण योग्य कोड कैसे लिखा जाए, और डेटाबेस ड्राइवर या वेब सर्वर को आसानी से कैसे बदला जाए। Rust पर जटिल, रखरखाव योग्य सिस्टम बनाने के इच्छुक किसी भी व्यक्ति के लिए यह एक अनिवार्य कौशल है।
\n\nHexagonal आर्किटेक्चर क्या है? (बहुत संक्षेप में)
\n\nएक षट्भुज (हेक्सागोन) की कल्पना करें। केंद्र में आपका बिजनेस लॉजिक (डोमेन) है। किनारों पर "पोर्ट्स" (इंटरफेस) हैं, जिनके माध्यम से डोमेन बाहरी दुनिया से संवाद करता है। और बाहर की तरफ "एडेप्टर" (विशिष्ट कार्यान्वयन) हैं, जो पोर्ट्स से जुड़ते हैं।
\n\nमुख्य विचार: डोमेन को यह नहीं पता होता कि डेटा कैसे सेव किया जाता है या अनुरोध कैसे भेजे जाते हैं। यह केवल पोर्ट्स के बारे में जानता है - विधियों वाले ट्रेट्स। एडेप्टर (जैसे PostgreSQL रिपॉजिटरी या HTTP कंट्रोलर) इन ट्रेट्स को लागू करते हैं।
\n\nRust में, यह अवधारणा ट्रेट्स (इंटरफेस के समान) और जेनेरिक्स के कारण पूरी तरह से फिट बैठती है। कंपाइलर रनटाइम के बजाय बिल्ड समय पर सभी कनेक्शनों की जांच करता है।
\n\nRust में प्रोजेक्ट की संरचना
\n\nशुरुआत करने के लिए, एक फोल्डर संरचना बनाएं जो डोमेन को इन्फ्रास्ट्रक्चर से दृष्टिगत रूप से अलग करे:
\n\nsrc/\n├── domain/ # शुद्ध बिजनेस लॉजिक (कोई निर्भरता नहीं!)\n│ ├── mod.rs\n│ ├── models.rs # एंटिटीज और वैल्यू ऑब्जेक्ट्स\n│ └── services.rs # पोर्ट्स के साथ काम करने वाली सेवाएं\n├── ports/ # बाहरी दुनिया के लिए ट्रेट्स (इंटरफेस)\n│ ├── mod.rs\n│ └── repositories.rs\n├── adapters/ # पोर्ट्स के कार्यान्वयन (डेटाबेस, HTTP, आदि)\n│ ├── mod.rs\n│ ├── postgres_repo.rs\n│ └── in_memory_repo.rs # परीक्षण के लिए\n├── main.rs # एप्लिकेशन असेंबली (वायरिंग)\n└── lib.rs # लाइब्रेरी का सार्वजनिक API\n\nचरण 1: डोमेन - सिस्टम का हृदय
\n\nएक सरल उपयोगकर्ता मॉडल से शुरू करें। डोमेन में बाहरी लाइब्रेरी (यहां तक कि serde भी नहीं, जब तक कि बिजनेस लॉजिक को इसकी आवश्यकता न हो) पर कोई निर्भरता नहीं है।
\n\n// src/domain/models.rs\npub struct User {\n pub id: String,\n pub name: String,\n pub email: String,\n}\n\nimpl User {\n pub fn new(id: String, name: String, email: String) -> Self {\n // यहां मान्यता हो सकती है\n Self { id, name, email }\n }\n}\n\nअब एक डोमेन सेवा परिभाषित करें। यह उपयोगकर्ताओं के साथ काम करेगी, लेकिन यह नहीं जानेगी कि वे कहां से आते हैं।
\n\n// src/domain/services.rs\nuse crate::ports::repositories::UserRepository;\n\npub struct UserService {\n repo: Box<dyn UserRepository>,\n}\n\nimpl UserService {\n pub fn new(repo: Box<dyn UserRepository>) -> Self {\n Self { repo }\n }\n\n pub fn create_user(&mut self, name: String, email: String) -> Result<User, String> {\n let id = uuid::Uuid::new_v4().to_string(); // क्या यह निर्भरता है? हाँ, लेकिन यह एक विवरण है\n let user = User::new(id, name, email);\n self.repo.save(&user)?;\n Ok(user)\n }\n\n pub fn get_user(&self, id: &str) -> Result<Option<User>, String> {\n self.repo.find_by_id(id)\n }\n}\n\nमहत्वपूर्ण: हम डायनामिक डिस्पैच के लिए Box<dyn UserRepository> का उपयोग कर रहे हैं। जटिल एप्लिकेशन के लिए Rust में यह सामान्य है। सरल मामलों के लिए, जेनेरिक्स का उपयोग किया जा सकता है।