डीडीडी एंटिटी मेथड्स कॉल्स पर सेवाएं देना


11

प्रश्न का लघु प्रारूप

इकाई विधि कॉल पर सेवाओं को इंजेक्ट करने के लिए डीडीडी और ओओपी की सर्वोत्तम प्रथाओं के भीतर है?

लंबा प्रारूप उदाहरण

मान लीजिए कि हमारे पास DDD में क्लासिक ऑर्डर-लाइनइमेट्स मामला है, जहां हमारे पास एक डोमेन एंटिटी है जिसे ऑर्डर कहा जाता है, जो एग्रीगेट रूट के रूप में भी कार्य करता है, और यह कि एंटिटी न केवल इसके मूल्य वस्तुओं में शामिल है, बल्कि लाइन आइटम का एक संग्रह भी है इकाइयों के लिए।

मान लें कि हम अपने आवेदन में धाराप्रवाह वाक्य रचना चाहते हैं, ताकि हम ऐसा कुछ कर सकें (पंक्ति 2 में वाक्य रचना को ध्यान में रखते हुए, जहाँ हम getLineItemsविधि कहते हैं ):

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
  ...
}

हम किसी भी प्रकार के LineItemRepository को OrderEntity में इंजेक्ट नहीं करना चाहते हैं, क्योंकि यह कई सिद्धांतों का उल्लंघन है, जिनके बारे में मैं सोच सकता हूं। लेकिन, वाक्यविन्यास का प्रवाह कुछ ऐसा है जिसे हम वास्तव में चाहते हैं, क्योंकि यह पढ़ना और बनाए रखना आसान है, साथ ही साथ परीक्षण भी।

निम्नलिखित कोड पर विचार करें, जिसमें विधि नोट की getLineItemsगई है OrderEntity:

interface IOrderService {
    public function getOrderByID($orderID) : OrderEntity;
    public function getLineItems(OrderEntity $orderEntity) : LineItemCollection;
}

class OrderService implements IOrderService {
    private $orderRepository;
    private $lineItemRepository;

    public function __construct(IOrderRepository $orderRepository, ILineItemRepository $lineItemRepository) {
        $this->orderRepository = $orderRepository;
        $this->lineItemRepository = $lineItemRepository;
    }

    public function getOrderByID($orderID) : OrderEntity {
        return $this->orderRepository->getByID($orderID);
    }

    public function getLineItems(OrderEntity $orderEntity) : LineItemCollection {
        return $this->lineItemRepository->getLineItemsByOrderID($orderEntity->ID());
    }
}

class OrderEntity {
    private $ID;
    private $lineItems;

    public function getLineItems(IOrderServiceInternal $orderService) {
        if(!is_null($this->lineItems)) {
            $this->lineItems = $orderService->getLineItems($this);
        }
        return $this->lineItems;
    }
}

क्या डीडीडी और ओओपी के मुख्य सिद्धांतों का उल्लंघन किए बिना एंटिटीज में धाराप्रवाह सिंटैक्स को लागू करने का स्वीकृत तरीका है? मेरे लिए यह ठीक लगता है, क्योंकि हम केवल सेवा परत को उजागर कर रहे हैं, न कि अवसंरचना परत (जो सेवा के भीतर निहित है)

जवाबों:


9

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

class Invoice
{
    private $currency;
    private $customerId;

    public function __construct()
    {
    }

    public function sum(InvoiceCalculator $calculator)
    {
        $sum =
            new SumRecord(
                $calculator->calculate($this)
            )
        ;

        if ($sum->isZero()) {
            $this->events->add(new ZeroSumCalculated());
        }

        return $sum;
    }
}

एक अन्य दृष्टिकोण हालांकि एक व्यावसायिक-तर्क को अलग करना है जो डोमेन घटनाओं के माध्यम से डोमेन सेवा में स्थित है । ध्यान रखें कि यह दृष्टिकोण सिर्फ अलग-अलग एप्लिकेशन सेवाओं से संबंधित है, लेकिन समान डेटाबेस लेनदेन गुंजाइश है।

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


6

यहां के कुछ जवाबों को पढ़कर मैं स्तब्ध हूं।

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

यह गलत है कि इसके निर्माणकर्ता के माध्यम से समुच्चय में सेवाओं को इंजेक्ट किया जाए। लेकिन एक व्यवसाय विधि के माध्यम से यह ठीक है और पूरी तरह से सामान्य है।


1
जो मामला आपने दिया है वह डोमेन सेवा की ज़िम्मेदारी क्यों नहीं होगा?
e_i_pi

1
यह एक डोमेन सेवा है, लेकिन यह व्यवसाय विधि में अंतःक्षिप्त है। आवेदन की परत सिर्फ एक ऑर्केस्ट्रेटर है,
diegosasw

मुझे डीडीडी में अनुभव नहीं है, लेकिन आवेदन सेवा से डोमेन सेवा को नहीं बुलाया जाना चाहिए और डोमेन सेवा सत्यापन के बाद उस एप्लिकेशन सेवा के माध्यम से एंटिटी विधियों को कॉल करना जारी रखना चाहिए? मैं अपनी परियोजना में इसी मुद्दे का सामना कर रहा हूं, क्योंकि डोमेन सेवा रिपॉजिटरी के माध्यम से डेटाबेस कॉल चलाता है ... मुझे नहीं पता कि क्या यह ठीक है।
मुफ्लिक्स

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

5

इकाई विधि कॉल पर सेवाओं को इंजेक्ट करने के लिए डीडीडी और ओओपी की सर्वोत्तम प्रथाओं के भीतर है?

नहीं, आपको अपने डोमेन लेयर के अंदर कुछ भी इंजेक्ट नहीं करना चाहिए (इसमें इकाइयां, मूल्य ऑब्जेक्ट, फैक्ट्री और डोमेन सेवाएं शामिल हैं)। यह परत किसी भी ढांचे, 3 पार्टी पुस्तकालयों या प्रौद्योगिकी के अज्ञेय होना चाहिए और किसी भी आईओ कॉल नहीं करना चाहिए।

$order->getLineItems($orderService)

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

  1. आपकी एग्रीगेट की सीमाएँ गलत हैं, वे बहुत बड़ी हैं।

  2. इस usecase में आप केवल पढ़ने के लिए एग्रीगेट का उपयोग करते हैं। सबसे अच्छा उपाय यह है कि रीड मॉडल से रीड मॉडल को विभाजित किया जाए (यानी CQRS का उपयोग करें )। इस क्लीनर आर्किटेक्चर में आपको एग्रीगेट लेकिन एक पढ़े हुए मॉडल को क्वेरी करने की अनुमति नहीं है।


अगर मुझे सत्यापन के लिए डेटाबेस कॉल की आवश्यकता है, तो मुझे इसे एप्लिकेशन सेवा में कॉल करना होगा और डोमेन सेवा के लिए या सीधे एग्रीगेट रूट में पास करना होगा, बल्कि फिर रिपॉजिटरी को डोमेन सेवा में इंजेक्ट करना होगा?
मुफ्लिक्स

1
@ मुफ़्लिक्स हाँ, यह सही है
कॉन्स्टेंटिन गैलबेनु

3

डीडीडी सामरिक पैटर्न में मुख्य विचार: एप्लिकेशन एक समग्र रूट पर कार्य करके एप्लिकेशन के सभी डेटा तक पहुंचता है। इसका तात्पर्य है कि केवल इकाइयाँ डोमेन मॉडल के बाहर पहुंच योग्य मूल जड़ें हैं।

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

मान लौटानाकुल के भीतर से ठीक है, क्योंकि मूल्य स्वाभाविक रूप से अपरिवर्तनीय हैं; आप इसकी प्रति बदलकर मेरा डेटा नहीं बदल सकते।

एक डोमेन सेवा का उपयोग तर्क के रूप में करना, सही मान प्रदान करने में समुच्चय की सहायता करना, एक पूरी तरह से उचित काम है।

आप सामान्य रूप से एग्रीगेट के अंदर मौजूद डेटा तक पहुंच प्रदान करने के लिए एक डोमेन सेवा का उपयोग नहीं करेंगे, क्योंकि एग्रीगेट के पास पहले से ही पहुंच होनी चाहिए।

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
  ...
}

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

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

बेशक, यह पूर्व-मान लेता है कि लाइन आइटम पहले से लोड किए गए हैं।

सामान्य पैटर्न यह है कि कुल भार में विशेष उपयोग के मामले में आवश्यक सभी राज्य शामिल होंगे। दूसरे शब्दों में, आपके पास एक ही कुल लोड करने के कई अलग-अलग तरीके हो सकते हैं ; आपके रिपॉजिटरी तरीके उद्देश्य के लिए फिट हैं ।

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


इसके लिए धन्यवाद। मैंने अब "लाल किताब" के बारे में आधा पढ़ा है, और बुनियादी ढांचे की परत में हॉलीवुड सिद्धांत को ठीक से लागू करने का मेरा पहला स्वाद था। इन सभी उत्तरों को फिर से पढ़ते हुए, वे सभी अच्छे अंक बनाते हैं, लेकिन मुझे लगता है कि आपके पास lineItems()एग्रिगेट रूट की पहली पुनर्प्राप्ति पर स्कोप और प्रीलोडिंग के बारे में कुछ बहुत महत्वपूर्ण बिंदु हैं ।
e_i_pi

3

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

आदेश ORM के मामले में, OrderRepository के बुनियादी ढांचे के कार्यान्वयन के बारे में, यह एक-से-कई संबंध हैं, और आप ऑर्डरलाइन को उत्सुकतापूर्वक या आलसी लोड कर सकते हैं।

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


2

इसका उत्तर है: निश्चित रूप से NO, इकाई विधियों में सेवाओं को पारित करने से बचें।

समाधान सरल है: बस ऑर्डर रिपॉजिटरी को ऑर्डर को उसके सभी LineItems से वापस करने दें। आपके मामले में एग्रीगेट ऑर्डर + लाइनइटीम्स है, इसलिए यदि रिपॉजिटरी पूरा एग्रीगेट नहीं लौटाती है, तो वह अपना काम नहीं कर रही है।

व्यापक सिद्धांत यह है: कार्यात्मक बिट्स (जैसे डोमेन लॉजिक) को गैर-कार्यात्मक बिट्स (जैसे, दृढ़ता) से अलग रखें।

एक और बात: यदि आप ऐसा करने से बचने की कोशिश कर सकते हैं:

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

इसके बजाय ऐसा करें

$order = $orderService->getOrderByID($orderID);
$order->doSomethingSignificant();

ऑब्जेक्ट-ओरिएंटेड डिज़ाइन में, हम ऑब्जेक्ट डेटा में मछली पकड़ने से बचने की कोशिश करते हैं। हम जो चाहते हैं वह करने के लिए वस्तु पूछना पसंद करते हैं।

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