डोमेन से रिपोजिटरी तक पहुँचना


14

कहें कि हमारे पास एक कार्य लॉगिंग सिस्टम है, जब कोई कार्य लॉग किया जाता है, तो उपयोगकर्ता एक श्रेणी निर्दिष्ट करता है और कार्य 'बकाया' की स्थिति के लिए डिफॉल्ट करता है। इस उदाहरण में मान लें कि श्रेणी और स्थिति को संस्थाओं के रूप में लागू किया जाना है। आम तौर पर मैं यह करूंगा:

अनुप्रयोग परत:

public class TaskService
{
    //...

    public void Add(Guid categoryId, string description)
    {
        var category = _categoryRepository.GetById(categoryId);
        var status = _statusRepository.GetById(Constants.Status.OutstandingId);
        var task = Task.Create(category, status, description);
        _taskRepository.Save(task);
    }
}

निकाय:

public class Task
{
    //...

    public static void Create(Category category, Status status, string description)
    {
        return new Task
        {
            Category = category,
            Status = status,
            Description = descrtiption
        };
    }
}

मैं इसे इस तरह से करता हूं क्योंकि मुझे लगातार कहा जाता है कि संस्थाओं को रिपॉजिटरी तक नहीं पहुंचना चाहिए, लेकिन अगर मैंने ऐसा किया तो यह मेरे लिए बहुत मायने रखेगा:

निकाय:

public class Task
{
    //...

    public static void Create(Category category, string description)
    {
        return new Task
        {
            Category = category,
            Status = _statusRepository.GetById(Constants.Status.OutstandingId),
            Description = descrtiption
        };
    }
}

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

यहां एक अधिक चरम उदाहरण है, यहां डोमेन तात्कालिकता तय करता है:

निकाय:

public class Task
{
    //...

    public static void Create(Category category, string description)
    {
        var task = new Task
        {
            Category = category,
            Status = _statusRepository.GetById(Constants.Status.OutstandingId),
            Description = descrtiption
        };

        if(someCondition)
        {
            if(someValue > anotherValue)
            {
                task.Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.UrgentId);
            }
            else
            {
                task.Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.SemiUrgentId);
            }
        }
        else
        {
            task.Urgency = _urgencyRepository.GetById
                (Constants.Urgency.NotId);
        }

        return task;
    }
}

ऐसा कोई तरीका नहीं है जिससे आप उर्जेंसी के सभी संभावित संस्करणों में पास होना चाहते हैं, और कोई भी तरीका नहीं जिसे आप इस व्यावसायिक तर्क को एप्लिकेशन लेयर में आंकना चाहें, तो निश्चित रूप से यह सबसे उपयुक्त तरीका होगा?

तो क्या यह डोमेन से रिपॉजिटरी तक पहुँचने का एक वैध कारण है?

संपादित करें: यह गैर स्थैतिक तरीकों पर भी हो सकता है:

public class Task
{
    //...

    public void Update(Category category, string description)
    {
        Category = category,
        Status = _statusRepository.GetById(Constants.Status.OutstandingId),
        Description = descrtiption

        if(someCondition)
        {
            if(someValue > anotherValue)
            {
                Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.UrgentId);
            }
            else
            {
                Urgency = _urgencyRepository.GetById
                    (Constants.Urgency.SemiUrgentId);
            }
        }
        else
        {
            Urgency = _urgencyRepository.GetById
                (Constants.Urgency.NotId);
        }

        return task;
    }
}

जवाबों:


8

आप रुक-रुक कर कर रहे हैं

संस्थाओं को रिपॉजिटरी तक नहीं पहुंचना चाहिए

(जो एक अच्छा सुझाव है)

तथा

डोमेन परत को रिपॉजिटरी तक नहीं पहुंचना चाहिए

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

यदि आप उस सृजन तर्क को इकाई वर्ग की स्थैतिक पद्धति में नहीं रखना चाहते हैं, तो आप अलग-अलग कारखाना वर्ग (डोमेन परत के भाग के रूप में) का परिचय दे सकते हैं और सृजन तर्क को वहाँ रख सकते हैं।

संपादित करें: आपके Updateउदाहरण के लिए: जो दिए गए हैं _urgencyRepositoryऔर statusRepositoryवर्ग के सदस्य हैं Task, जिन्हें किसी प्रकार के इंटरफ़ेस के रूप में परिभाषित किया गया है, अब आपको उन्हें किसी भी Taskइकाई में इंजेक्ट करने की आवश्यकता है इससे पहले कि आप Updateअब उपयोग कर सकते हैं (उदाहरण के लिए कार्य निर्माणकर्ता)। या आप उन्हें स्थिर सदस्यों के रूप में परिभाषित करते हैं, लेकिन सावधान रहें, जो एक ही समय में अलग-अलग टास्क संस्थाओं के लिए अलग-अलग रिपॉजिटरी की आवश्यकता होने पर आसानी से मल्टी थ्रेडिंग समस्या, या सिर्फ समस्याएं पैदा कर सकते हैं।

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

महत्वपूर्ण हिस्सा है: TaskUpdaterअभी भी डोमेन परत का हिस्सा होगा! सिर्फ इसलिए कि आपने उस अपडेट या निर्माण कोड को एक अलग वर्ग में रखा है, इसका मतलब यह नहीं है कि आपको दूसरी परत पर स्विच करना होगा।


मैंने यह दिखाने के लिए संपादन किया है कि यह गैर-स्टैटिक विधियों पर उतना ही लागू होता है जितना कि स्टैटिक वाले। मैंने कभी नहीं सोचा था कि कारखाना विधि एक इकाई का हिस्सा नहीं है।
पॉल टी डेविस

@PaulTDavies: मेरा संपादन देखें
डॉक्टर ब्राउन

मैं इस बात से सहमत हूं कि आप यहां क्या कह रहे हैं, लेकिन मैं एक संक्षिप्त अंश जोड़ूंगा जो उस बिंदु को खींचता है, जो Status = _statusRepository.GetById(Constants.Status.OutstandingId)एक व्यावसायिक नियम है , जिसे आप "व्यापार को सभी कार्यों की प्रारंभिक स्थिति को निर्धारित करता है" उत्कृष्ट होगा। वह कोड लाइन एक रिपॉजिटरी के अंदर नहीं है, जिसकी एकमात्र चिंता CRUD ऑपरेशंस के जरिए डेटा मैनेजमेंट है।
जिमी हॉफ

@ जिमीहॉफ: एचएम, यहां कोई भी रिपॉजिटरी क्लास में से एक में उस तरह की लाइन डालने का सुझाव नहीं दे रहा था, न तो ओपी और न ही मुझे - तो आपकी बात क्या है?
डॉक ब्राउन

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

6

मुझे नहीं पता कि आपकी स्थिति उदाहरण वास्तविक कोड है या यहाँ केवल प्रदर्शन के लिए है, लेकिन मुझे यह अजीब लगता है कि आपको स्टेटस को एक इकाई के रूप में लागू करना चाहिए (एक सकल रूट का उल्लेख नहीं करना चाहिए) जब इसकी आईडी एक निरंतर परिभाषित होती है कोड में - Constants.Status.OutstandingId। डेटाबेस में आप जितने चाहें उतने "स्टेटस" उद्देश्यों को पराजित नहीं कर सकते हैं?

मुझे लगता है कि आपके मामले में, एक का निर्माण Task(यदि आवश्यक हो तो StatusRepository से सही स्थिति प्राप्त करने का काम भी शामिल है) TaskFactoryका निर्माण Taskस्वयं में रहने के बजाय हो सकता है, क्योंकि यह एक गैर-तुच्छ वस्तुओं का संयोजन है।

परंतु :

मुझे लगातार बताया जाता है कि संस्थाओं को रिपॉजिटरी तक नहीं पहुंचना चाहिए

यह कथन सबसे बेहतर, भ्रामक और सबसे खराब, खतरनाक है।

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

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

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

इसलिए एक इकाई से रिपॉजिटरी तक पहुंचना अपने आप में बुरा नहीं है , यह विभिन्न रूप ले सकता है जो विभिन्न प्रकार के डिजाइन निर्णयों के परिणामस्वरूप भयावह से स्वीकार्य हो सकते हैं।


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

यदि आपने मुझे अच्छी तरह पढ़ा है, तो हम इस पर पूरी तरह सहमत हैं ...
guillaume31

2

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

public class Interval
{
  public Interval(DateTime start, DateTime? end)
  {
    Start=start;
    End=end;
  }

  //To be called by internal framework
  protected Interval()
  {
  }

  public void End(DateTime? when=null)
  {
    if(when==null)
      when=DateTime.Now;
    End=when;
  }

  public DateTime Start{get;protected set;}

  public DateTime? End{get; protected set;}
}

public class TaskStatus
{
  protected TaskStatus()
  {
  }
  public Long Id {get;protected set;}

  public string Name {get; protected set;}

  public string Description {get; protected set;}

  public Interval Duration {get; protected set;}

  public virtual TNewStatus TransitionTo<TNewStatus>()
    where TNewStatus:TaskStatus
  {
    throw new NotImplementedException();
  }
}

public class OutStandingTaskStatus:TaskStatus
{
  protected OutStandingTaskStatus()
  {
  }

  public OutStandingTaskStatus(bool initialize)
  {
    Name="Oustanding";
    Description="For tasks that need to be addressed";
    Duration=new Interval(DateTime.Now,null);
  }

  public override TNewStatus TransitionTo<TNewStatus>()
  {
    if(typeof(TNewStatus)==typeof(CompletedTaskStatus))
    {
      var transitionDate=DateTime.Now();
      Duration.End(transitionDate);
      return new CompletedTaskStatus(true);
    }
    return base.TransitionTo<TNewStatus>();
  }
}

कम्प्लीटेडटैस्कस्टैटस का कार्यान्वयन बहुत अधिक समान होगा।

यहाँ ध्यान देने योग्य बातें हैं:

  1. मैं डिफॉल्ट कंस्ट्रक्टर को संरक्षित करता हूं। ऐसा तब होता है जब किसी ऑब्जेक्ट को दृढ़ता (दोनों EntityFramework Code-First और NHibernate का उपयोग करते हैं, जो आपके डोमेन ऑब्जेक्ट्स से उनके जादू करने के लिए प्राप्त होते हैं) से खींचते समय इसे कॉल कर सकते हैं।

  2. कई संपत्ति बसने वालों को उसी कारण से संरक्षित किया जाता है। यदि मैं किसी अंतराल की अंतिम तिथि को बदलना चाहता हूं, तो मुझे Interval.End () फ़ंक्शन को कॉल करना होगा (यह डोमेन ड्रिवेन डिज़ाइन का हिस्सा है, एनीमिक डोमेन ऑब्जेक्ट्स के बजाय सार्थक संचालन प्रदान करता है।

  3. मैं इसे यहां नहीं दिखाता, लेकिन टास्क इसी तरह से विवरणों को छिपाएगा कि यह अपनी वर्तमान स्थिति को कैसे संग्रहीत करता है। मेरे पास आमतौर पर हिस्टोरिकलस्टेट्स की एक संरक्षित सूची है जो मैं जनता को क्वेरी करने की अनुमति देता हूं यदि वे रुचि रखते हैं। अन्यथा मैं एक गटर के रूप में वर्तमान स्थिति को उजागर करता हूं जो कि हिस्टोरिकलस्टेट्स.सिंगल (स्थिति। अवधि। End == null) को क्वेरी करता है।

  4. TransitionTo फ़ंक्शन महत्वपूर्ण है क्योंकि इसमें तर्क हो सकता है कि कौन से राज्य संक्रमण के लिए मान्य हैं। यदि आपके पास बस एक दुश्मनी है, तो उस तर्क को कहीं और झूठ बोलना होगा।

उम्मीद है, इससे आपको DDD के दृष्टिकोण को थोड़ा बेहतर समझने में मदद मिलेगी।


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

1

मैं कुछ समय से एक ही समस्या को हल करने की कोशिश कर रहा हूं, मैंने फैसला किया कि मैं टास्क को कॉल करना चाहता हूं। यूटडेटटैस्क () उस तरह से, हालांकि मैं इसके बजाय डोमेन विशिष्ट होगा, आपके मामले में शायद मैं इसे टास्क कहूंगा। (...) एक कार्रवाई को इंगित करने के लिए और न केवल CRUD।

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UnitTestProject2
{
    public class ClientCode
    {
        public void Main()
        {
            TaskFactory factory = new TaskFactory();
            Task task = factory.Create();
            task.UpdateTask(new Category(), "some value");
        }

    }
    public class Category
    {
    }

    public class Task
    {
        public Action<Category, String> UpdateTask { get; set; }

        public static void UpdateTaskAction(Task task, Category category, string description)
        {
            // do the logic here, static can access private if needed
        }
    }

    public class TaskFactory
    {      
        public Task Create()
        {
            Task task = new Task();
            task.UpdateTask = (category, description) =>
                {
                    Task.UpdateTaskAction(task, category, description);
                };

            return task;
        }

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