सेवा लॉजिक में व्यावसायिक तर्क को स्थानांतरित किए बिना डोमेन ऑब्जेक्ट विशेषताओं पर अद्वितीय विरोधाभासों की जांच करने का एक सुंदर तरीका है?


10

मैं अब लगभग 8 वर्षों से डोमेन-संचालित डिज़ाइन को अपना रहा हूं और इन सभी वर्षों के बाद भी, अभी भी एक चीज है, जो मुझे परेशान कर रही है। यह एक डोमेन ऑब्जेक्ट के खिलाफ डेटा स्टोरेज में एक अद्वितीय रिकॉर्ड के लिए जाँच कर रहा है।

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

मेरी परियोजनाओं को आम तौर पर कई हिस्सों में विभाजित किया जाता है, जहां उनमें से दो डोमेन होते हैं (व्यावसायिक नियम और कुछ नहीं, डोमेन पूरी तरह से अज्ञान-अज्ञानी है) और सेवाएं। CRUD डेटा के लिए उपयोग की जाने वाली रिपॉजिटरी लेयर के बारे में जानने वाली सेवाएँ।

चूँकि किसी ऑब्जेक्ट से संबंधित विशेषता की विशिष्टता एक डोमेन / व्यावसायिक नियम है, यह डोमेन मॉड्यूल के लिए लंबा होना चाहिए, इसलिए यह नियम ठीक वही है जहां इसे माना जाता है।

किसी रिकॉर्ड की विशिष्टता की जांच करने में सक्षम होने के लिए, आपको वर्तमान डेटासेट, आमतौर पर एक डेटाबेस, यह पता लगाने की आवश्यकता है कि क्या कोई दूसरा रिकॉर्ड Nameपहले से मौजूद है।

डोमेन परत को ध्यान में रखते हुए अज्ञानता है और यह पता नहीं है कि डेटा को कैसे पुनर्प्राप्त करना है, लेकिन केवल उन पर संचालन कैसे करना है, यह वास्तव में स्वयं रिपॉजिटरी को नहीं छू सकता है।

फिर जिस डिजाइन को मैं अपना रहा हूं वह इस तरह दिखता है:

class ProductRepository
{
    // throws Repository.RecordNotFoundException
    public Product GetBySKU(string sku);
}

class ProductCrudService
{
    private ProductRepository pr;

    public ProductCrudService(ProductRepository repository)
    {
        pr = repository;
    }

    public void SaveProduct(Domain.Product product)
    {
        try {
            pr.GetBySKU(product.SKU);

            throw Service.ProductWithSKUAlreadyExistsException("msg");
        } catch (Repository.RecordNotFoundException e) {
            // suppress/log exception
        }

        pr.MarkFresh(product);
        pr.ProcessChanges();
    }
}

इससे डोमेन लेयर के बजाए डोमेन नियमों को परिभाषित करने वाली सेवाएँ हो जाती हैं और आपके कोड के कई सेक्शनों में नियम बिखर जाते हैं।

मैंने टेलडॉट एएएस सिद्धांत का उल्लेख किया है, क्योंकि जैसा कि आप स्पष्ट रूप से देख सकते हैं, सेवा एक कार्रवाई प्रदान करती है (यह या तो Productअपवाद को बचाता है या एक अपवाद फेंकता है), लेकिन विधि के अंदर आप प्रक्रियात्मक दृष्टिकोण का उपयोग करके ऑब्जेक्ट पर काम कर रहे हैं।

स्पष्ट समाधान यह है कि फेंकने की विधि के Domain.ProductCollectionसाथ एक वर्ग बनाया जाए , लेकिन इसमें प्रदर्शन की बहुत कमी है, क्योंकि आपको कोड में पता लगाने के लिए डेटा संग्रहण से सभी उत्पाद प्राप्त करने की आवश्यकता होगी, चाहे एक उत्पाद पहले से ही SKU हो उत्पाद के रूप में आप जोड़ने की कोशिश कर रहे हैं।Add(Domain.Product)ProductWithSKUAlreadyExistsException

आप लोग इस विशिष्ट मुद्दे को कैसे हल करते हैं? यह वास्तव में प्रति से एक समस्या नहीं है, मैंने वर्षों तक सेवा परत को कुछ डोमेन नियमों का प्रतिनिधित्व किया है। सेवा परत आमतौर पर अधिक जटिल डोमेन संचालन भी करती है, मैं बस सोच रहा हूं कि क्या आप अपने करियर के दौरान बेहतर, अधिक केंद्रीकृत, समाधान पर ठोकर खा चुके हैं।


जब आप सिर्फ एक के लिए पूछ सकते हैं तो नामों की एक पूरी सूची क्यों खींच सकते हैं और पाया गया या नहीं इस पर तर्क को आधार बना सकते हैं? आप दृढ़ता परत को बता रहे हैं कि क्या करना है और कैसे नहीं करना है जब तक कि इंटरफ़ेस के साथ एक समझौता नहीं है।
जेफो

1
सेवा शब्द का उपयोग करते समय, हमें अधिक स्पष्टता की ओर प्रयास करना चाहिए, जैसे कि डोमेन परत में डोमेन सेवाओं और एप्लिकेशन परत में एप्लिकेशन सेवाओं के बीच अंतर। देखें gorodinski.com/blog/2012/04/14/...
एरिक Eidt

एक्सेलेंट लेख, @EricEidt, धन्यवाद। मुझे लगता है कि मेरा डिजाइन गलत नहीं है, अगर मैं श्री गोरोडिंस्की पर भरोसा कर सकता हूं, तो वह बहुत कुछ कह रहा है: एक बेहतर समाधान एक आवेदन सेवा है जो एक इकाई द्वारा आवश्यक जानकारी को पुनः प्राप्त करता है, प्रभावी ढंग से निष्पादन पर्यावरण की स्थापना करता है, और प्रदान करता है। यह इकाई के लिए है, और मेरे पास सीधे डोमेन मॉडल में रिपॉजिटरी को इंजेक्ट करने के खिलाफ एक ही ऑब्जेक्ट है, ज्यादातर एसआरपी को तोड़ने के माध्यम से।
एंडी १

"बताओ, मत पूछो" संदर्भ से एक उद्धरण: "लेकिन व्यक्तिगत रूप से, मैं बता न का उपयोग न करें-पूछें।"
राडारबॉब

जवाबों:


7

डोमेन परत को ध्यान में रखते हुए अज्ञानता है और यह पता नहीं है कि डेटा को कैसे पुनर्प्राप्त करना है, लेकिन केवल उन पर संचालन कैसे करना है, यह वास्तव में स्वयं रिपॉजिटरी को नहीं छू सकता है।

मैं इस हिस्से से असहमत होगा। खासतौर पर आखिरी वाक्य।

हालांकि यह सच है कि डोमेन को अज्ञानता से दूर रहना चाहिए, यह जानता है कि "डोमेन संस्थाओं का संग्रह" है। और वहाँ डोमेन नियम हैं जो इस संग्रह को समग्र रूप से चिंतित करते हैं। विशिष्टता उनमें से एक है। और क्योंकि वास्तविक तर्क का कार्यान्वयन विशेष दृढ़ता मोड पर निर्भर करता है, इसलिए इस तर्क के लिए आवश्यक निर्दिष्ट डोमेन में किसी प्रकार का अमूर्त होना चाहिए।

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

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


इनपुट के लिए धन्यवाद। क्या रिपॉजिटरी वास्तव में Domain.ProductCollectionमेरे मन में प्रतिनिधित्व कर सकती है, यह विचार करते हुए कि वे डोमेन लेयर से ऑब्जेक्ट्स को पुनः प्राप्त करने के लिए जिम्मेदार हैं?
एंडी

@ डैविडपैकर मुझे वास्तव में समझ में नहीं आ रहा है कि आपका क्या मतलब है। क्योंकि स्मृति में सभी वस्तुओं को रखने की आवश्यकता नहीं होनी चाहिए। "DoNameExist" विधि का कार्यान्वयन डेटा स्टोर की तरफ (शायद सबसे) SQL क्वेरी होना चाहिए।
व्यंग्यात्मक

मतलब क्या है, मेमोरी में एक संग्रह में डेटा संग्रहीत करने के बजाय, जब मैं सभी उत्पाद जानना चाहता हूं, तो मुझे उन्हें प्राप्त करने की आवश्यकता नहीं है, Domain.ProductCollectionबल्कि इसके बजाय रिपॉजिटरी से पूछें, क्या यह पूछने के साथ कि क्या Domain.ProductCollectionएक उत्पाद है पारित SKU, इस बार, फिर से, रिपॉजिटरी के बजाय (यह वास्तव में उदाहरण है) पूछ रहा है, जो पूर्व-लोड उत्पादों पर पुनरावृत्ति करने के बजाय अंतर्निहित डेटाबेस पर सवाल उठाता है। मेरा मतलब है कि जब तक मेरे पास सभी प्रोडक्ट स्टोर नहीं होंगे, तब तक ऐसा करना पूरी तरह बकवास होगा।
एंडी १

जो एक और सवाल की ओर जाता है, तो भंडार को पता होना चाहिए कि क्या एक विशेषता अद्वितीय होनी चाहिए? मैंने हमेशा रिपॉजिटरी को बहुत गूंगा घटकों के रूप में लागू किया है, जो आप उन्हें बचाने के लिए पास करते हैं और पारित स्थितियों के आधार पर डेटा को पुनः प्राप्त करने और निर्णय लेने की कोशिश करते हैं।
एंडी १

@DavidPacker खैर, "डोमेन ज्ञान" इंटरफ़ेस में है। इंटरफ़ेस कहता है "डोमेन को इस कार्यक्षमता की आवश्यकता है", और डेटा स्टोर फिर उस कार्यक्षमता को प्रदान करता है। यदि आपके पास IProductRepositoryइंटरफ़ेस है DoesNameExist, तो यह स्पष्ट है कि विधि क्या करना चाहिए। लेकिन यह सुनिश्चित करना कि उत्पाद का एक ही नाम नहीं हो सकता है क्योंकि मौजूदा उत्पाद डोमेन में होना चाहिए।
व्योम

4

आपको ग्रेग यंग को सेट मान्यता पर पढ़ने की आवश्यकता है ।

संक्षिप्त उत्तर: इससे पहले कि आप चूहों के घोंसले से बहुत दूर जाएं, आपको यह सुनिश्चित करने की आवश्यकता है कि आप व्यवसाय के दृष्टिकोण से आवश्यकता के मूल्य को समझते हैं। कितना महंगा है, वास्तव में, नकल को रोकने और उसे कम करने के बजाय इसका पता लगाना?

"विशिष्टता" आवश्यकताओं के साथ समस्या यह है कि, ठीक है, बहुत अक्सर एक गहरा अंतर्निहित कारण है कि लोग उन्हें क्यों चाहते हैं - यव्स रेनहॉउट

लंबे समय तक उत्तर: मैंने संभावनाओं का एक मेनू देखा है, लेकिन वे सभी ट्रेडऑफ़ हैं।

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

class Product {
    void register(SKU sku, DuplicationService skuLookup) {
        if (skuLookup.isKnownSku(sku) {
            throw ProductWithSKUAlreadyExistsException(...)
        }
        ...
    }
}

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

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

आप अपने डोमेन में एक अलग एग्रीगेट बना सकते हैं जो ज्ञात स्की के सेट का प्रतिनिधित्व करता है। मैं यहां दो भिन्नताओं के बारे में सोच सकता हूं।

एक ProductCatalog की तरह कुछ है; उत्पाद कहीं और मौजूद होते हैं, लेकिन उत्पादों और स्कोस के बीच संबंध को एक कैटलॉग द्वारा बनाए रखा जाता है जो स्कू विशिष्टता की गारंटी देता है। ऐसा नहीं है कि इसका मतलब यह है कि उत्पादों में स्कीस नहीं है; स्केस को एक ProductCatalog द्वारा सौंपा गया है (यदि आपको अद्वितीय होने के लिए स्कोस की आवश्यकता है, तो आप इसे केवल एक एकल ProductCatalog एग्रीगेट प्राप्त करके प्राप्त कर सकते हैं)। अपने डोमेन विशेषज्ञों के साथ सर्वव्यापी भाषा की समीक्षा करें - यदि ऐसा कुछ मौजूद है, तो यह सही दृष्टिकोण हो सकता है।

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

आप सभी उत्पाद संस्थाओं को एक एकल में खींच सकते हैं - अर्थात, ऊपर वर्णित उत्पाद सूची। जब आप ऐसा करते हैं, तो आप पूरी तरह से स्कीस की विशिष्टता प्राप्त करते हैं, लेकिन लागत अतिरिक्त विवाद है, किसी भी उत्पाद को संशोधित करने का मतलब वास्तव में पूरी सूची को संशोधित करना है।

मुझे इन-मेमोरी में ऑपरेशन करने के लिए सभी उत्पाद SKU को डेटाबेस से बाहर खींचने की आवश्यकता नहीं है।

शायद आप की जरूरत नहीं है यदि आप ब्लूम फ़िल्टर के साथ अपने स्कू का परीक्षण करते हैं , तो आप सेट पर लोड किए बिना कई अद्वितीय स्कोस की खोज कर सकते हैं।

यदि आपका उपयोग मामला आपको मनमानी करने की अनुमति देता है कि आप किस स्केस को अस्वीकार करते हैं, तो आप सभी झूठी सकारात्मकता को दूर कर सकते हैं (यदि आप क्लाइंट को कमांड सबमिट करने से पहले प्रस्तावित स्कोस का परीक्षण करने की अनुमति देते हैं तो कोई बड़ी बात नहीं है)। यह आपको स्मृति में सेट को लोड करने से बचने की अनुमति देगा।

(यदि आप अधिक स्वीकार करना चाहते हैं, तो आप लोम फिल्टर में मैच की स्थिति में स्केज लोड करने पर विचार कर सकते हैं; आप अभी भी सभी स्कोस को कभी-कभी मेमोरी में लोड करने का जोखिम उठाते हैं, लेकिन अगर आप अनुमति दें तो यह सामान्य मामला नहीं होना चाहिए। क्लाइंट कोड भेजने से पहले त्रुटियों के लिए कमांड की जांच करने के लिए)।


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

शायद एक ब्लूम फ़िल्टर (संपादित करें देखें)।
VoiceOfUnreason
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.