जब सामान्य CRUD पर्याप्त नहीं है: CQRS और Event Sourcing से परिचय
\nकल्पना करें कि आप एक टिकट बुकिंग सिस्टम बना रहे हैं। जब तक दस उपयोगकर्ता हैं, सब कुछ सरल है। लेकिन जब वे एक हज़ार हो जाते हैं, और फिर एक मिलियन, समस्याएँ शुरू हो जाती हैं: डेटाबेस लॉक हो जाते हैं, क्वेरीज़ संघर्ष करती हैं, और परिवर्तनों का इतिहास पुनर्स्थापित करना असंभव हो जाता है। परिचित स्थिति?
\nGo-डेवलपमेंट की दुनिया में, जहाँ प्रदर्शन और समानता हमारा सब कुछ है, दो आर्किटेक्चरल पैटर्न इन समस्याओं को हल करने में मदद करते हैं: CQRS (Command Query Responsibility Segregation) और Event Sourcing। आज हम उन्हें व्यवहार में समझेंगे — कोड, उदाहरणों और वास्तविक उपयोग परिदृश्यों के साथ।
\n\nसरल शब्दों में CQRS क्या है?
\nCQRS कमांड (लेखन) और क्वेरीज़ (पढ़ना) के बीच जिम्मेदारी का विभाजन है। क्लासिक CRUD में, एक डेटा मॉडल निर्माण और पढ़ने दोनों के लिए जिम्मेदार होता है। CQRS में, हम दो अलग-अलग मॉडल बनाते हैं:
\n- \n
- Command model — स्थिति बदलने के लिए जिम्मेदार (निर्माण, अद्यतन, हटाना) \n
- Query model — केवल डेटा पढ़ने के लिए जिम्मेदार \n
यह प्रत्येक पक्ष को स्वतंत्र रूप से अनुकूलित करने की अनुमति देता है: लेखन सख्ती से मान्य हो सकता है, जबकि पढ़ना डीनॉर्मलाइज़्ड और तेज़ हो सकता है। Go में, गोरूटीन और चैनलों के कारण यह विशेष रूप से प्रभावी है।
\n\nGo पर CQRS का सरल उदाहरण
\nचलिए ऑर्डर प्रबंधन के लिए एक बुनियादी सिस्टम बनाते हैं। कमांड से शुरू करते हैं:
\n\ntype CreateOrderCommand struct {\n OrderID string\n UserID string\n ProductID string\n Amount float64\n}\n\ntype CommandHandler struct {\n repo *OrderRepository\n}\n\nfunc (h *CommandHandler) HandleCreateOrder(ctx context.Context, cmd CreateOrderCommand) error {\n order := Order{\n ID: cmd.OrderID,\n UserID: cmd.UserID,\n ProductID: cmd.ProductID,\n Amount: cmd.Amount,\n Status: "pending",\n CreatedAt: time.Now(),\n }\n return h.repo.Save(ctx, order)\n}\n\nअब पढ़ने के लिए एक अलग मॉडल बनाते हैं:
\n\ntype OrderQuery struct {\n repo *ReadRepository\n}\n\ntype OrderView struct {\n OrderID string\n UserName string\n ProductName string\n Total float64\n Status string\n}\n\nfunc (q *OrderQuery) GetOrderSummary(ctx context.Context, orderID string) (*OrderView, error) {\n return q.repo.GetOrderView(ctx, orderID)\n}\n\nध्यान दें: OrderView में पहले से ही डीनॉर्मलाइज़्ड डेटा (उपयोगकर्ता का नाम, उत्पाद का नाम) है, जो विभिन्न स्रोतों से एकत्र किया गया है। यह पढ़ने को दस गुना तेज़ बनाता है।
Event Sourcing: स्थिति नहीं, बल्कि घटनाएँ रिकॉर्ड करना
\nEvent Sourcing डेटा भंडारण के दृष्टिकोण को बदलता है: किसी वस्तु की वर्तमान स्थिति को संग्रहीत करने के बजाय, हम उन घटनाओं का क्रम संग्रहीत करते हैं जो उस स्थिति तक ले गईं। यह एक सुपरपावर देता है: परिवर्तनों का पूरा इतिहास और स्थिति को पीछे "रिवाइंड" करने की क्षमता।
\n\nGo में Event Sourcing का कार्यान्वयन
\nचलिए बुनियादी घटना प्रकारों से शुरू करते हैं:
\n\ntype Event struct {\n ID string\n AggregateID string\n Type string\n Data []byte\n Timestamp time.Time\n Version int\n}\n\ntype EventStore interface {\n Save(ctx context.Context, events []Event) error\n Load(ctx context.Context, aggregateID string) ([]Event, error)\n}\n\nअब हम एग्रीगेट को लागू करते हैं — एक ऑब्जेक्ट जो घटनाओं से अपनी स्थिति पुनर्स्थापित करता है:
\n\ntype OrderAggregate struct {\n ID string\n Status string\n Items []OrderItem\n Version int\n}\n\nfunc (a *OrderAggregate) Apply(event Event) {\n switch event.Type {\n case "OrderCreated":\n var data OrderCreatedData\n json.Unmarshal(event.Data, &data)\n a.ID = data.OrderID\n a.Status = "created"\n case "OrderConfirmed":\n a.Status = "confirmed"\n case "OrderShipped":\n a.Stat