जब सामान्य CRUD काम करना बंद कर दे: CQRS और Event Sourcing का परिचय
\n\nकल्पना करें कि आप एक बैंक के लिए सिस्टम लिख रहे हैं। हर सेकंड सैकड़ों उपयोगकर्ता बैलेंस चेक कर रहे हैं, पैसे ट्रांसफर कर रहे हैं, FD खोल रहे हैं। आपका C# एप्लिकेशन सामान्य Entity Framework और CRUD ऑपरेशन पर चल रहा है। और अचानक — डेटाबेस क्रैश हो जाता है। क्यों? क्योंकि एक भारी रिपोर्ट ने टेबल को लॉक कर दिया, जबकि ट्रांजेक्शन लिखा जा रहा था। परिचित लग रहा है?
C# में जटिल सिस्टम बनाने वाले डेवलपर्स जल्दी या बाद में क्लासिकल आर्किटेक्चर की सीमाओं से टकराते हैं। यहाँ दो शक्तिशाली पैटर्न मदद करते हैं: CQRS (Command Query Responsibility Segregation) और Event Sourcing। आइए इन्हें प्रैक्टिकल उदाहरणों के साथ समझें — सिर्फ थ्योरी नहीं।
\n\nCQRS क्या है और यह "सिर्फ अलग करना" क्यों नहीं है?
\n\nCQRS — यह सिर्फ मेथड्स को अलग-अलग फोल्डर में रखना नहीं है। यह एक फिलॉसफी है: कमांड्स (स्टेट बदलते हैं) और क्वेरीज़ (स्टेट पढ़ते हैं) को अलग रहना चाहिए। क्लासिकल CRUD में आपके पास पढ़ने और लिखने के लिए एक ही डेटा मॉडल होता है। CQRS में — कम से कम दो।
क्लासिकल अप्रोच की समस्या
\nमान लीजिए, आपके पास UserRepository है जिसमें GetUserById मेथड है। जब आप यूज़र को स्क्रीन पर दिखाने के लिए पढ़ते हैं, तो आपको सिर्फ 3 फील्ड चाहिए। जब आप अपडेट करते हैं — 10 फील्ड और चेकिंग। नतीजा: एक मॉडल null फील्ड और उलझे हुए मैपर्स वाला राक्षस बन जाता है।
CQRS में आप जिम्मेदारी बांटते हैं:
\n- \n
- Command — एक एक्शन करता है (जैसे,
CreateOrderCommand) \n - Query — डेटा लौटाता है (जैसे,
GetOrderQuery) \n - Read Model — एक सपाट DTO, जो किसी विशेष स्क्रीन के लिए ऑप्टिमाइज़ है \n
- Write Model — बिज़नेस लॉजिक वाला समृद्ध डोमेन मॉडल \n
C# में MediatR के साथ CQRS लिखना
\nC# में CQRS लागू करने का सबसे लोकप्रिय तरीका MediatR लाइब्रेरी का उपयोग करना है। यह Mediator पैटर्न को लागू करता है और कमांड और क्वेरी की अवधारणा पर पूरी तरह फिट बैठता है।
चरण 1: कमांड को परिभाषित करें
\npublic record CreateOrderCommand(\n Guid CustomerId,\n List<OrderItemDto> Items,\n string ShippingAddress\n) : IRequest<Guid>; // बनाए गए ऑर्डर का ID लौटाता है\n\nचरण 2: कमांड हैंडलर
\npublic class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid>\n{\n private readonly IOrderRepository _orderRepository;\n private readonly IUnitOfWork _unitOfWork;\n\n public CreateOrderCommandHandler(IOrderRepository orderRepository, IUnitOfWork unitOfWork)\n {\n _orderRepository = orderRepository;\n _unitOfWork = unitOfWork;\n }\n\n public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken)\n {\n // बिज़नेस लॉजिक: जाँच, वैलिडेशन, क्रिएशन\n var order = new Order(request.CustomerId, request.ShippingAddress);\n \n foreach (var item in request.Items)\n {\n order.AddItem(item.ProductId, item.Quantity, item.Price);\n }\n\n await _orderRepository.AddAsync(order);\n await _unitOfWork.SaveChangesAsync(cancellationToken);\n \n return order.Id;\n }\n}\n\nचरण 3: क्वेरी को परिभाषित करें
\npublic record GetOrderSummaryQuery(Guid OrderId) : IRequest<OrderSummaryDto>;\n\nचरण 4: क्वेरी हैंडलर (अलग DB से पढ़ता है!)
\npublic class GetOrderSummaryQueryHandler : IRequestHandler<GetOrderSummaryQuery, OrderSummaryDto>\n{\n private readonly IReadDbContext _readDbContext; // पढ़ने के लिए अलग कॉन्टेक्स्ट\n\n public GetOrderSummaryQueryHandler(IReadDbContext read