परिचय: जब CRUD काम करना बंद कर देता है
\n\nकल्पना करें कि आप शेयर बाजार के लिए एक सिस्टम लिख रहे हैं। हर सेकंड - हजारों ऑर्डर। हर ऑर्डर सिर्फ "रिकॉर्ड डालना" नहीं है। यह बैलेंस चेक, पोर्टफोलियो पुनर्गणना, ऑडिट, नोटिफिकेशन है। और अब कल्पना करें कि आपको न केवल वर्तमान स्थिति रिकॉर्ड करनी है, बल्कि यह भी पुनर्स्थापित करना है कि कल 14:35:22.003 पर क्या हुआ था। सामान्य CRUD (Create, Read, Update, Delete) यहाँ विफल हो जाता है। जावा में CQRS और इवेंट सोर्सिंग की दुनिया में आपका स्वागत है।
\n\nये दो पैटर्न सिर्फ फैशनेबल शब्द नहीं हैं। ये ऐसे उपकरण हैं जो आपको उच्च प्रदर्शन, पूर्ण ऑडिट और "समय को पीछे ले जाने" की क्षमता वाले सिस्टम बनाने की अनुमति देते हैं। इस लेख में, हम जावा में CQRS (Command Query Responsibility Segregation) और इवेंट सोर्सिंग को आधुनिक फ्रेमवर्क और स्वच्छ कोड का उपयोग करके लागू करने का तरीका समझेंगे। बिना अनावश्यक बातों के, उदाहरणों के साथ।
\n\nसरल शब्दों में CQRS क्या है
\n\nCQRS कमांड (लेखन) और क्वेरी (पढ़ने) के बीच जिम्मेदारी का पृथक्करण है। क्लासिक CRUD में, आप पढ़ने और लिखने के लिए एक ही मॉडल का उपयोग करते हैं। CQRS में, आप दो अलग-अलग मॉडल बनाते हैं: एक डेटा बदलने के लिए (कमांड), दूसरा डेटा निकालने के लिए (क्वेरी)।
\n\nइसकी आवश्यकता क्यों है?
\n- \n
- प्रदर्शन: पढ़ना और लिखना स्वतंत्र रूप से स्केल करते हैं। पढ़ने के लिए 10 रेप्लिका और लिखने के लिए 2 रेप्लिका हो सकती हैं। \n
- सुरक्षा: कमांड व्यावसायिक नियमों की जांच कर सकते हैं, जबकि क्वेरी केवल डेटा प्रदान करती हैं। \n
- लचीलापन: पढ़ने के लिए डीनॉर्मलाइज़्ड व्यू का उपयोग किया जा सकता है, लिखने के लिए नॉर्मलाइज़्ड का। \n
कोड उदाहरण: कमांड और क्वेरी का पृथक्करण
\n\nमान लीजिए, हमारे पास ऑर्डर प्रबंधन प्रणाली है। OrderService.save(order) करने के बजाय, हम अलग करते हैं:
// कमांड - स्थिति बदलने का इरादा है\npublic class CreateOrderCommand {\n private final String orderId;\n private final String customerId;\n private final List<OrderItem> items;\n\n // कंस्ट्रक्टर, गेटर्स...\n}\n\n// कमांड हैंडलर\n@Component\npublic class CreateOrderCommandHandler {\n @Autowired\n private OrderRepository orderRepository;\n\n public void handle(CreateOrderCommand command) {\n // व्यावसायिक नियमों का सत्यापन\n if (command.getItems().isEmpty()) {\n throw new IllegalArgumentException("ऑर्डर में कम से कम एक आइटम होना चाहिए");\n }\n Order order = new Order(command.getOrderId(), command.getCustomerId(), command.getItems());\n orderRepository.save(order);\n }\n}\n\n// क्वेरी - डेटा प्राप्त करना है\npublic class GetOrderQuery {\n private final String orderId;\n // कंस्ट्रक्टर, गेटर्स...\n}\n\n// क्वेरी हैंडलर\n@Component\npublic class GetOrderQueryHandler {\n @Autowired\n private OrderReadRepository orderReadRepository;\n\n public OrderDTO handle(GetOrderQuery query) {\n // ऑप्टिमाइज़्ड DB से सीधा पढ़ना\n return orderReadRepository.findById(query.getOrderId());\n }\n}\n\nध्यान दें: OrderRepository (लेखन) और OrderReadRepository (पढ़ना) अलग-अलग इंटरफेस हैं। वे अलग-अलग डेटाबेस या अलग-अलग स्कीमा की ओर इशारा कर सकते हैं।
इवेंट सोर्सिंग: स्थिति नहीं, इतिहास संग्रहीत करें
\n\nइवेंट सोर्सिंग एक पैटर्न है जिसमें हम किसी ऑब्जेक्ट की वर्तमान स्थिति नहीं, बल्कि उस स्थिति तक ले जाने वाली घटनाओं के अनुक्रम को संग्रहीत करते हैं। "ऑर्डर #123: स्थिति = DELIVERED" के बजाय हम संग्रहीत करते हैं: "इवेंट 1: ऑर्डर बनाया गया", "इवेंट 2: उत्पाद जोड़ा गया", "इवेंट 3: ऑर्डर का भुगतान किया गया", "इवेंट 4: ऑर्डर वितरित किया गया"।
\n\nयह जावा में कैसे काम करता है
\n\nप्रत्येक इवेंट एक अपरिवर्तनीय (immutable) ऑब्जेक्ट है। हम उन्हें इवेंट स्टोर (एक विशेष DB) में संग्रहीत करते हैं। एग्रीगेट की वर्तमान स्थिति सभी इवेंट्स को पुनः चलाकर (replaying) गणना की जाती है।
\n\n// बेस इवेंट\npublic interface Do