2012-08-23 13 views
12

वर्तमान में हमने काम पर एक भंडार पैटर्न लागू किया है। हमारे सभी भंडार अपने स्वयं के इंटरफेस के पीछे बैठते हैं और निनजेक्ट के माध्यम से मैप किए जाते हैं। हमारी परियोजना काफी बड़ी है और इस पैटर्न के साथ कुछ कर्कश हैं जिन्हें मैं हल करने की कोशिश कर रहा हूं।हमारे भंडार पैटर्न को सरल बनाने की कोशिश

सबसे पहले, कुछ नियंत्रक हैं जहां हमें एक ही नियंत्रक में 10 से 15 रिपॉजिटरीज़ की आवश्यकता होती है। इतने सारे भंडारों के लिए पूछते समय कन्स्ट्रक्टर बदसूरत हो जाता है। एकाधिक रिक्तियों को कई रिपॉजिटरीज़ पर कॉल करने के बाद खुद को प्रकट करता है। एकाधिक भंडारों के साथ काम करने के बाद हमें SaveChanges विधि को कॉल करने की आवश्यकता है, लेकिन हमें इसे किस भंडार पर कॉल करना चाहिए? प्रत्येक भंडार में एक है। सभी रिपॉजिटरीज में एंटिटी फ्रेमवर्क डेटा संदर्भ इंजेक्शन का एक ही उदाहरण होता है, इसलिए काम पर कॉल को बचाने के लिए किसी भी यादृच्छिक भंडार को चुनना। यह बस इतना गन्दा लगता है।

मैंने "यूनिट ऑफ वर्क" पैटर्न देखा और एक समाधान के साथ आया जो मुझे लगता है कि दोनों समस्याएं हल करती हैं, लेकिन मुझे इस समाधान में 100% आत्मविश्वास नहीं है, इसलिए किसी भी प्रतिक्रिया की सराहना की जाती है। मैंने DataBucket नामक एक कक्षा बनाई (ज्यादातर क्योंकि मुझे इसे UnitOfWork पर कॉल करना पसंद नहीं है। यह मजाकिया लगता है।)।

// Slimmed down for readability 
public class DataBucket 
{ 
    private DataContext _dataContext; 

    public IReportsRepository ReportRepository { get; set; } 
    public IEmployeeRepository EmployeeRepository { get; set; } 
    public IDashboardRepository DashboardRepository { get; set; } 

    public DataBucket(DataContext dataContext, 
     IReportsRepository reportsRepository, 
     IEmployeeRepository employeeRepository, 
     IDashboardRepository dashboardRepository) 
    { 
     _dataContext = dataContext; 
     this.ReportRepository = reportsRepository; 
     this.EmployeeRepository = employeeRepository; 
     this.DashboardRepository = dashboardRepository; 
    } 

    public void SaveChanges() 
    { 
     _dataContext.SaveChanges(); 
    } 
} 

यह दोनों मुद्दों को हल करने के लिए प्रतीत होता है। डेटा बाल्टी पर केवल एक ही SaveChanges विधि है और आप केवल एक ऑब्जेक्ट, डेटा बाल्टी इंजेक्ट करते हैं। फिर आप सभी रिपॉजिटरीज को गुणों के रूप में एक्सेस करते हैं। डेटा बाल्टी थोड़ा गन्दा लग रहा है क्योंकि यह अपने कन्स्ट्रक्टर में हमारे भंडारों के सभी (आसानी से 50 या अधिक) को स्वीकार करेगा।

एक नया भंडार जोड़ने की प्रक्रिया में अब शामिल होगा: इंटरफ़ेस बनाना, भंडार बनाना, इंटरफ़ेस को मैप करना और निनजेक्ट में भंडार, और डेटा बाल्टी में एक संपत्ति जोड़ना और इसे पॉप्युलेट करना।

मैंने इस विकल्प के बारे में सोचा जो ऊपर से एक कदम को खत्म कर देगा।

public class DataBucket 
{ 
    private DataContext _dataContext; 

    public IReportsRepository ReportRepository { get; set; } 
    public IEmployeeRepository EmployeeRepository { get; set; } 
    public IDashboardRepository DashboardRepository { get; set; } 

    public DataBucket(DataContext dataContext) 
    { 
     _dataContext = dataContext; 
     this.ReportRepository = new ReportsRepository(dataContext); 
     this.EmployeeRepository = new EmployeeRepository(dataContext); 
     this.DashboardRepository = new DashboardRepository(dataContext); 
    } 

    public void SaveChanges() 
    { 
     _dataContext.SaveChanges(); 
    } 
} 

यह एक काफी Ninject में सभी भंडार मैपिंग क्योंकि वे सभी डेटा बाल्टी में instantiated कर रहे हैं दूर करता है। तो अब एक नया भंडार जोड़ने के लिए कदमों में शामिल हैं: इंटरफ़ेस बनाएं, भंडार बनाएं, डेटा बाल्टी में संपत्ति जोड़ें और तत्काल करें।

क्या आप इस मॉडल के साथ कोई दोष देख सकते हैं? सतह पर यह हमारे भंडारों को इस तरह से उपभोग करने के लिए और अधिक सुविधाजनक लगता है। क्या यह एक समस्या है जिसे पहले संबोधित किया गया है? यदि हां, तो इस मुद्दे के लिए सबसे आम और/या सबसे प्रभावी दृष्टिकोण क्या है?

धन्यवाद :)

+0

क्या आपको आईडीटाबकेट इंटरफ़ेस की आवश्यकता नहीं है क्योंकि यह कक्षा रिपोजिटरी-क्लासेस का उपयोग करती है, इसलिए इस कक्षा को भी इंजेक्शन दिया जाता है? वह तरफ, मेरे लिए एक अच्छा समाधान की तरह लगता है। मुझे लगता है कि भंडार-गुणों को निजी सेटर बनाना बेहतर होगा। – Maarten

+1

आप निनजेक्ट के साथ ठोस कक्षाओं को इंजेक्ट कर सकते हैं। यदि आप कोई मैपिंग प्रदान नहीं करते हैं तो निनजेक्ट अनुरोध किए गए नाम के साथ एक कक्षा की तलाश करेगा और यदि संभव हो तो इसे तुरंत चालू करें। – Chev

+0

यूनिट उद्देश्यों के लिए हालांकि @ मार्टन आप शायद सही हैं। मुझे इसे इंटरफ़ेस करना होगा ताकि मैं परीक्षण के लिए इसे नकल कर सकूं। – Chev

उत्तर

2

मुझे लगता है कि आप इस मामले में कार्य पैटर्न के यूनिट का उपयोग करने के लिए बिल्कुल सही हैं। न केवल यह आपको प्रत्येक भंडार पर SaveChanges विधि की आवश्यकता से रोकता है, यह आपको अपने डेटाबेस के बजाय कोड के भीतर लेनदेन को संभालने का एक अच्छा तरीका प्रदान करता है। मैंने अपने यूओओ के साथ Rollback विधि शामिल की ताकि अगर कोई अपवाद हो तो मैं अपने DataContext पर पहले से ही किए गए किसी भी बदलाव को पूर्ववत कर सकता हूं।

अजीब निर्भरता के मुद्दों को रोकने के लिए आप जो कुछ कर सकते हैं, वह समूह की संबंधित रिपॉजिटरीज को अपने स्वयं के कार्य इकाई पर एक बड़ी डेटाबकेट रखने की बजाय, जो आपके पास हर रिपोजिटरी है (यदि वह आपका इरादा था)। प्रत्येक यूओओ को केवल उसी स्तर पर पहुंचने की आवश्यकता होगी, जिसमें भंडार शामिल हैं, और अन्य भंडार शायद अन्य यूओओ पर निर्भर नहीं होना चाहिए (आपके भंडारों को अन्य भंडारों का उपयोग करने की आवश्यकता नहीं है)।

यदि पैटर्न के एक और बड़े शुद्धिकरण बनना चाहते हैं, तो आप अपने यूओयू को केवल उस इकाई का प्रतिनिधित्व करने के लिए तैयार कर सकते हैं, जो कार्य का एक इकाई है। आप उन्हें अपने डोमेन में एक विशिष्ट संचालन का प्रतिनिधित्व करने के लिए परिभाषित करते हैं, और इसे उस ऑपरेशन को पूरा करने के लिए आवश्यक भंडार प्रदान करते हैं। व्यक्तिगत रिपोजिटरी एक से अधिक यूओयू पर मौजूद हो सकती है, अगर यह आपके डोमेन में एक से अधिक ऑपरेशन द्वारा उपयोग की जाने वाली समझ में आती है।

उदाहरण के लिए, एक PlaceCustomerOrderUnitOfWork एक CustomerRepository, OrderRepository, BillingRepository, और एक ShippingRepository

एक CreateCustomerUnitOfWork सिर्फ एक CustomerRepository आवश्यकता हो सकती है आवश्यकता हो सकती है। किसी भी तरह से, आप अपने उपभोक्ताओं के आस-पास उस निर्भरता को आसानी से पारित कर सकते हैं, आपके यूओओ के लिए अधिक बढ़िया अनाज वाले इंटरफेस आपके परीक्षण को लक्षित करने और नकली बनाने के प्रयास को कम करने में मदद कर सकते हैं।

+0

दिलचस्प अंतर्दृष्टि। यदि मैं सब कुछ युक्त एक डेटा बाल्टी के अपने मूल विचार के साथ जाता हूं, तो क्या आपको तत्काल संबंधित सभी रिपॉजिटरीज रखने की समस्या दिखाई देती है? एक और समाधान रेपो को नया करने के लिए पहली बार होगा जब यूओयू पर संपत्ति का उपयोग किया जाता है। [इस तरह कुछ] (https://gist.github.com/3440615)। इस तरह के लेनदेन के दौरान केवल उन रिपोप्स की आवश्यकता होती है जिन्हें तत्काल किया जाएगा। तुम क्या सोचते हो? – Chev

+0

@AlexFord यह ठीक है कि मैंने "आलसी लोडिंग" रिपोजिटरीज 'अगर (repo == null) फिर // instantiate' द्वारा मेरा कार्यान्वित किया। एक मोनोलिथिक इंटरफ़ेस बनाने के अलावा, एक डेटाबकेट में सब कुछ रखने से आपको ठीक होना चाहिए, जिससे आप अपने सभी कार्यान्वयन कार्यान्वयन पर एक ही असेंबली में जा रहे हैं, जो आपके कार्य कार्यान्वयन के यूनिट के रूप में है। ऐसा नहीं है कि आप उन्हें अलग-अलग नहीं रख सकते हैं, हालांकि यह आपको अजीब चक्रीय निर्भरताओं की संभावना के लिए खोलता है। – mclark1129

+0

हाँ सब कुछ हम यहां चर्चा कर रहे हैं हमारे "डोमेन" असेंबली में किया जाता है, इसलिए हमें वहां चिंता करने की ज़रूरत नहीं है। आपके सहयोग के लिए धन्यवाद। आप बहुत मददगार रहे हैं। – Chev

1

होने एक SaveChanges त्रुटिपूर्ण है क्योंकि यह बुला सब कुछ बचाता है हर भंडार की धारणा। DataContext के भाग को संशोधित करना संभव नहीं है, आप हमेशा सबकुछ बचाते हैं। तो एक केंद्रीय DataContext धारक वर्ग एक अच्छा विचार है।

वैकल्पिक रूप से, आपके पास जेनेरिक विधियों के साथ एक भंडार हो सकता है जो कि किसी भी इकाई प्रकार (GetTable<T>, Query<T>, ...) पर संचालित हो सकता है। इससे सभी उन वर्गों से छुटकारा पड़ेगा और उन्हें एक में विलय कर देगा (मूल रूप से, केवल DataBucket अवशेष)।

यह भी मामला हो सकता है कि आपको रिपॉजिटरीज़ की आवश्यकता नहीं है: आप DataContext को इंजेक्ट कर सकते हैं! DataContext अपने आप से एक भंडार और एक पूर्ण डेटा पहुंच परत है। हालांकि यह खुद को मजाक करने के लिए उधार नहीं देता है।

यदि आप ऐसा कर सकते हैं तो आपको "भंडार" प्रदान करने की आवश्यकता पर निर्भर करता है।


होने DataBucket वर्ग होगा कि इस वर्ग संस्थाओं और सभी खजाने के बारे में सभी जानते हैं की जरूरत है साथ ही मुद्दा। तो यह सॉफ़्टवेयर स्टैक (शीर्ष) पर बहुत अधिक बैठता है। साथ ही यह मूल रूप से सबकुछ द्वारा उपयोग किया जा रहा है, इसलिए यह नीचे पर भी बैठता है। रुकिए! यह पूरे कोडबेस पर निर्भरता चक्र है।

इसका मतलब है कि इसका उपयोग करने वाली हर चीज और इसके द्वारा उपयोग की जा रही सब कुछ एक ही असेंबली में बैठनी चाहिए।

+0

ठीक है, भंडार स्तर savechanges कोई मतलब नहीं है। यही कारण है कि मैं इस से निपट रहा हूँ :)। मैं एक सामान्य भंडार के साथ झुका हुआ था और यह पता चला कि बहुत से भंडारों को एक सामान्य टेम्पलेट में फिट करने के लिए विशेष तरीकों और कार्यक्षमता की आवश्यकता है। मुझे रिपोजिटरी स्तर के ऊपर LINQ अभिव्यक्ति वृक्षों को भी बनाना पसंद नहीं है क्योंकि यह अबास्ट्रक्शन के उद्देश्य को हरा देता है; यह मूल रूप से ऊपरी परतों को एक डेटा परत पर निर्भर करता है जो हमेशा IQueryable के खिलाफ अभिव्यक्ति पेड़ को समझता है। – Chev

+0

दूसरी तरफ, रेपो के बाहर मनमाने ढंग से अभिव्यक्तियों की अनुमति न देने से आप सभी गैर-तुच्छ (इकाई-प्राप्त) उपयोग मामलों के लिए विशेष क्वेरी विधियों (और वापसी प्रकार कक्षाएं) जोड़ सकते हैं। यहाँ 300+ टेबल के साथ एक बड़े कोडेबेस के साथ अनुभव से बात करते हुए: भंडार पैटर इसके लायक नहीं है। हमने ओआरएम को कभी नहीं बदला। हमने कभी भी हमारे आरडीबीएमएस को नहीं बदला। हम कभी भी NoSQL पर स्विच करने का फैसला नहीं करते हैं। बस कभी नहीं होता है। एक भंडार abstraction कुछ भी उपयोगी नहीं जोड़ देगा, लेकिन प्रयासों और जटिलता पर जोड़ें। – usr

+0

आपकी स्थिति में मैं शायद सहमत हूं। दुर्भाग्यवश मुझे इस तरह की चिंताओं को अलग करने के साथ काम सौंपा गया है कि हम _could_ अगर हम चाहते हैं तो NoSQL पर स्विच करें। – Chev

0

मैंने अतीत में जो किया है वह बाल इंजेक्शन कंटेनर (मैं एकता का उपयोग कर रहा था) बनाना था और ContainerControlledLifetime के साथ डेटा संदर्भ पंजीकृत करना था। ताकि जब भंडार तुरंत चालू हो जाएं, तो हमेशा में समान डेटा संदर्भ इंजेक्शन दिया गया है। मैं फिर उस डेटा संदर्भ पर लटकता हूं और जब मेरा "कार्य इकाई" पूरा हो जाता है, तो मैं डेटाबेस में सभी परिवर्तनों को फ़्लश करने के लिए DataContext.SaveChanges() पर कॉल करता हूं।

इसमें कुछ अन्य फायदे हैं जैसे कि (ईएफ के साथ) कुछ स्थानीय कैशिंग, जैसे कि यदि एक से अधिक भंडारों को एक ही इकाई प्राप्त करने की आवश्यकता होती है, तो केवल पहला भंडार वास्तव में डेटाबेस राउंड ट्रिप का कारण बनता है।

यह परिवर्तनों को "बैच अप" करने का एक अच्छा तरीका भी है और सुनिश्चित करें कि वे एक परमाणु लेनदेन के रूप में निष्पादित करते हैं।

5

सबसे पहले, कुछ नियंत्रक हैं जहां हमें एक ही नियंत्रक में 10 से 15 रिपॉजिटरीज़ की आवश्यकता होती है।

सार कारखाने पैटर्न के लिए हैलो कहें। निनजेक्ट में सभी रिपॉजिटरीज को पंजीकृत करने और नियंत्रकों को इंजेक्शन देने के बजाय कारखाने के केवल एक ही कार्यान्वयन को पंजीकृत करें जो आपको आवश्यक किसी भी भंडार प्रदान करने में सक्षम होगा - आप उन्हें केवल आलसी बना सकते हैं यदि नियंत्रक को वास्तव में उनकी आवश्यकता हो। कारखाने को नियंत्रक से इंजेक्ट करने से।

हां इसके कुछ नुकसान भी हैं - आप किसी भी भंडार प्राप्त करने के लिए नियंत्रक अनुमति दे रहे हैं। क्या यह आपके लिए समस्या है? यदि आप एकल कार्यान्वयन पर कई फैक्ट्री इंटरफेस की आवश्यकता या बस खुलासा करते हैं तो आप हमेशा कुछ उप प्रणालियों के लिए कई कारखानों को बना सकते हैं। यह अभी भी सभी मामलों को कवर नहीं करता है लेकिन यह कन्स्ट्रक्टर को 15 पैरामीटर पास करने से बेहतर है। Btw। क्या आप वाकई उन नियंत्रकों को विभाजित नहीं किया जाना चाहिए?

नोट: यह सेवा प्रदाता विरोधी पैटर्न नहीं है।

एकाधिक भंडारों के साथ काम करने के बाद हमें SaveChanges विधि को कॉल करने की आवश्यकता है, लेकिन हमें इसे किस भंडार पर कॉल करना चाहिए?

कार्य पैटर्न की इकाई को नमस्ते कहें। कार्य की इकाई आपके आवेदन में तार्किक लेनदेन है। यह तार्किक लेनदेन से सभी परिवर्तनों को एक साथ रखता है। रिपोजिटरी लगातार परिवर्तनों के लिए ज़िम्मेदार नहीं होना चाहिए - काम की इकाई होना चाहिए। किसी ने उल्लेख किया कि DbContext रिपोजिटरी पैटर्न का कार्यान्वयन है। It is not। यह कार्य पैटर्न की इकाई का कार्यान्वयन है और DbSet रिपोजिटरी पैटर्न का कार्यान्वयन है।

आपको केंद्रीय वर्ग को संदर्भ के उदाहरण को रखने की आवश्यकता है। संदर्भ भंडारों को भी पास कर दिया जाएगा क्योंकि उन्हें डेटा पुनर्प्राप्त करने की आवश्यकता है, लेकिन केवल केंद्रीय वर्ग (कार्य की इकाई) बचत परिवर्तन की पेशकश करेगा। यदि आप उदाहरण के लिए अलगाव स्तर को बदलने की जरूरत है तो यह डेटाबेस लेनदेन को भी संभाल सकता है।

काम की इकाई कहाँ संभाली जानी चाहिए? That depends जहां आपका लॉजिकल ऑपरेशन ऑर्केस्ट्रेट किया गया है। यदि ऑपरेशन सीधे नियंत्रक के कार्यों में व्यवस्थित किया जाता है तो आपको कार्रवाई में कार्य की इकाई भी होनी चाहिए और सभी संशोधन किए जाने के बाद SaveChanges पर कॉल करें।

यदि आप चिंताओं को अलग करने की परवाह नहीं करते हैं तो आप combine unit of work and factory को एकल कक्षा में भी कर सकते हैं। यह हमें आपके DataBucket पर लाता है।

+0

आपका विचार मेरी संशोधित डेटा बाल्टी कक्षा के समान लगता है जहां मैं केवल आवश्यकतानुसार रिपोजिटरी को तुरंत चालू करता हूं। https://gist.github.com/3440615 आप इसके बारे में क्या सोचते हैं? या क्या आपके पास इस कारखाने को "भंडार" बनाने के लिए एक अलग विचार है? क्या इस कारखाने में 'CreateReportsRepository' विधि होगी? यदि ऐसा है तो मेरे पास बाल्टी के गुणों के समान ही तरीके होंगे। या क्या आप इसे एक विधि में अमूर्त सोच रहे हैं जो 'आईरिपोजिटरी' जैसी चीज़ को तुरंत चालू करेगी और फिर उसे इसकी आवश्यकता होगी जहां इसे जरूरी है? – Chev

+0

हां, आपके पास रिपोजिटरी के रूप में कई विधियों की आवश्यकता होगी। यह बदसूरत मात्रा में बढ़ सकता है - यह एक अलग कारखानों के बारे में सोचने का समय है। जब तक आपके पास सामान्य जेनेरिक इंटरफ़ेस को लागू करने वाला सामान्य भंडार नहीं होता है (जिसे मैं बुरा मानता हूं) तो आप इसे बेहतर नहीं बनायेंगे। एक ही सामान्य इंटरफ़ेस के साथ आप एक विधि के पीछे जटिलता को छिपाने का प्रयास कर सकते हैं लेकिन आपको अभी भी एक सही भंडार को तुरंत चालू करने के लिए एक तर्क की आवश्यकता है। –

+1

चलो खरगोश छेद के नीचे यह एक और कदम उठाएं। मान लें कि मैं अब दो कारखानों का निर्माण करता हूं जिनमें संबंधित भंडार शामिल हैं। मेरे आवेदन के कुछ हिस्सों में मुझे परिवर्तन करने के लिए उन दोनों कारखानों का उपयोग करने की आवश्यकता है। मैं कौन सा फैक्ट्री बदलता हूं? ऐसा लगता है कि मैंने रिपोजिटरी-स्तरीय सेव विधियों के समान समस्या को फिर से बनाया है। – Chev

संबंधित मुद्दे