إذا كنت مجبرًا على استخدام نموذج مجال فقر الدم ، فأين تضع منطق عملك والحقول المحسوبة؟

StackOverflow https://stackoverflow.com/questions/1933351

سؤال

لا تسمح أداة O/RM الحالية الخاصة بنا بنماذج مجال غنية ، لذلك نحن مجبرون على استخدام كيانات فقر الدم (DTO) في كل مكان. لقد نجح هذا بشكل جيد ، لكنني ما زلت أعاني من حيث وضع منطق الأعمال الأساسي القائم على الكائنات والحقول المحسوبة.

الطبقات الحالية:

  • عرض تقديمي
  • خدمة
  • مخزن
  • البيانات/الكيان

تحتوي طبقة المستودع لدينا على معظم المنطق/الحفاظ على صحة/حفظ أساسي ، على الرغم من أن طبقة الخدمة تقوم بالكثير من التحقق من الصحة والتوفير الأكثر تعقيدًا (نظرًا لأن عمليات التوفير تقوم أيضًا بالتسجيل ، والتحقق من الأذونات ، وما إلى ذلك). المشكلة هي مكان وضع رمز مثل هذا:

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}

أو

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}

أي أفكار؟

هل كانت مفيدة؟

المحلول

دعنا نعود إلى الأساسيات:

خدمات

تأتي الخدمات في 3 نكهات: خدمات المجال, خدمات التطبيق, ، و خدمات البنية التحتية

  • خدمات المجال : يغلف منطق العمل الذي لا يتناسب بشكل طبيعي مع كائن المجال. في حالتك، الكل من منطق عملك.
  • خدمات التطبيق : يستخدمه المستهلكون الخارجيون للتحدث إلى نظامك
  • خدمات البنية التحتية : تستخدم لتجريد المخاوف الفنية (مثل MSMQ ، مزود البريد الإلكتروني ، إلخ)

مخزن

هذا هو المكان الذي الوصول إلى البيانات و شيكات الاتساق يذهب. في DDD النقي ، الخاص بك الجذور الإجمالية سيكون مسؤولاً عن التحقق من الاتساق (قبل استمرار أي كائنات). في حالتك ، يمكنك استخدام الشيكات من خدمات المجال طبقة.


الحل المقترح: تقسيم خدماتك الحالية

استخدم جديدًا خدمات المجال طبقة لتغليف كل المنطق ل DTOs الخاص بك ، والتحقق من الاتساق أيضًا (باستخدام تحديد, ، يمكن؟).

استخدم ال خدمة التطبيق لفضح طرق الجلب اللازمة (FetchOpenOrdersWithLines) ، والتي توجيه الطلبات الخاصة بك مخزن (واستخدام الأدوية ، كما اقترح جيريمي). قد تفكر أيضًا في استخدام مواصفات الاستعلام لالتفاف استفساراتك.

منك مخزن, ، استعمال تحديد في الخاص بك خدمات المجال طبقة للتحقق من اتساق الكائن وما إلى ذلك قبل استمرار كائناتك.

يمكنك العثور على المعلومات الداعمة في كتاب إيفانز:

  • "الخدمات وطبقة المجال المعزولة" (ص 106)
  • "تحديد" (ص 224)
  • "مواصفات الاستعلام" (ص 229)

نصائح أخرى

أنا أميل إلى الإجابة مو, ، لكني أود توضيح. في تلخيص: لا تدع اختيارك لـ orm تملي كيف تحدد نموذج المجال الخاص بك.

الغرض من نموذج المجال هو أن تكون واجهة برمجة تطبيقات غنية موجهة نحو الكائنات التي تصمم المجال. لمتابعة صحيح تصميم يحركه المجال, ، يجب تعريف نموذج المجال غير مقيد بالتكنولوجيا.

وبعبارة أخرى ، يأتي نموذج المجال أولاً, ، ويتم معالجة جميع التطبيقات الخاصة بالتكنولوجيا لاحقًا بواسطة المحاسب تلك الخريطة بين نموذج المجال والتكنولوجيا المعنية. سيتضمن ذلك غالبًا كلا الاتجاهين: إلى طبقة الوصول إلى البيانات حيث قد يقدم اختيار ORM قيودًا ، وإلى طبقة واجهة المستخدم حيث تفرض تقنية واجهة المستخدم متطلبات إضافية.

إذا كان التنفيذ بعيدًا بشكل غير عادي عن نموذج المجال ، فإننا نتحدث عن طبقة مكافحة الفساد.

في حالتك ، فإن ما تسميه نموذج مجال فقر الدم هو في الواقع طبقة الوصول إلى البيانات. أفضل اللجوء لك هو تحديد المستودعات هذا النموذج الوصول إلى كياناتك بطريقة محايدة التكنولوجيا.

على سبيل المثال ، دعونا نلقي نظرة على كيان طلبك. قد يؤدي تصميم طلب غير مقيد بالتكنولوجيا إلى شيء مثل هذا:

public class Order
{
    // constructors and properties

    public decimal CalculateTotal()
    {
        return (from li in this.LineItems
                select li.CalculateTotal()).Sum();
    }
}

لاحظ أن هذا كائن CLR قديم عادي ( بوكو ) وبالتالي فهي غير مقيدة بالتكنولوجيا. الآن السؤال هو كيف تحصل على هذا داخل وخارج متجر البيانات الخاص بك؟

يجب أن يتم ذلك عبر مجردة iorderrepository:

public interface IOrderRepository
{
    Order SelectSingle(int id);

    void Insert(Order order);

    void Update(Order order);

    void Delete(int id);

    // more, specialized methods can go here if need be
}

يمكنك الآن تطبيق iorderRepository باستخدام ORM المفضل لديك. ومع ذلك ، فإن بعض ORMS (مثل إطار كيان Microsoft) يتطلب منك استخلاص فئات البيانات من فئات أساسية معينة ، لذلك لا يتناسب على الإطلاق مع كائنات المجال مثل POCOS. لذلك ، مطلوب رسم الخرائط.

الشيء المهم الذي يجب إدراكه هو أنه قد يكون لديك فئات بيانات مكتوبة بقوة لغويا تشبه كيانات المجال الخاصة بك. ومع ذلك ، فهذه تفاصيل تنفيذ خالصة ، لذلك لا تشوش من ذلك. فئة طلب مستمدة من EG entityObject ليست فئة مجال - إنها تفاصيل تنفيذ ، لذلك عند تنفيذ iorderrepository ، تحتاج إلى تعيين الطلب فئة البيانات للترتيب طبقة دومان.

قد يكون هذا عملًا شاقًا ، لكن يمكنك استخدامه السيارات للقيام بذلك من أجلك.

إليك كيف قد يبدو تنفيذ طريقة SelectSingle:

public Order SelectSinge(int id)
{
    var oe = (from o in this.objectContext.Orders
              where o.Id == id
              select o).First();
    return this.mapper.Map<OrderEntity, Order>(oe);
}

هذا هو بالضبط ما هي طبقة الخدمة - لقد رأيت أيضًا تطبيقات حيث تسمى طبقة BusinessLogic.

هذه هي الروتين الذي تريد قضاء معظم وقتك في الاختبار ، وإذا كانت في طبقتها الخاصة ، فيجب أن تكون طبقة المستودع واضحة.

يجب أن تكون طبقة المستودع عامة قدر الإمكان ، لذلك ليس مكانًا مناسبًا لمنطق العمل الذي يكون فرديًا إلى فئات معينة.

من ما تقوله قد يكون أنك تفكر بشكل صارم في خدمتك وطبقات المستودع. يبدو أنك لا تريد أن يكون لطبقة العرض التقديمي تبعية مباشرة على طبقة المستودع وتحقيق ذلك ، فأنت تكرر طرقًا من مستودعاتك (طرق النجاح الخاصة بك) في طبقة الخدمة.

أود أن أسأل ذلك. يمكنك الاسترخاء والسماح لاستخدام كلاهما في طبقة العرض التقديمي وجعل حياتك أكثر بساطة للبدء. ربما اسأل نفسك عن تحقيقك من خلال إخفاء المستودعات من هذا القبيل. أنت تقوم بالفعل باختصار الثبات والاستعلام عن التنفيذ معهم. هذا رائع وما تم تصميمه من أجله. يبدو أنك تحاول إنشاء طبقة خدمة تخفي حقيقة أن كياناتك مستمرة على الإطلاق. سأسأل لماذا؟

أما بالنسبة لحساب مجاميع الطلب وما إلى ذلك ، فإن طبقة الخدمة الخاصة بك ستكون المنزل الطبيعي. ستكون فئة SalesorderCalculator مع طرق LineTotal (LineItem LineItem) و OrderTotal (طلب الطلب) على ما يرام. قد ترغب أيضًا في التفكير في إنشاء مصنع مناسب لـ OrderServices.CreateDordRCalculator () لتحويل التنفيذ إذا لزم الأمر (خصم الضريبة على الطلب لديه قواعد محددة على سبيل المثال). قد يشكل هذا أيضًا نقطة دخول واحدة لطلب الخدمات وجعل العثور على الأشياء سهلة من خلال Intellisense.

إذا كان كل هذا يبدو غير قابل للتطبيق ، فقد تحتاج إلى التفكير بعمق أكبر في ما تحققه تجريداتك ، وكيفية ارتباطها ببعضها البعض مبدأ المسؤولية الفردية. المستودع هو تجريد البنية التحتية (إخفاء كيفية حفظ الكيانات واسترجاعها). الخدمات الملخص بعيدًا عن تنفيذ إجراءات العمل أو القواعد والسماح ببنية أفضل للإصدار أو التباين. إنهم ليسوا طبقة بشكل عام في طريقة تصفه. إذا كان لديك قواعد أمنية معقدة في خدماتك ، فقد تكون مستودعاتك هي المنزل الأفضل. في نموذج نمط DDD نموذجي ، سيتم استخدام جميع المستودعات والكيانات وكائنات القيمة والخدمات جنبًا إلى جنب مع بعضها البعض في نفس الطبقة وكجزء من نفس النموذج. وبالتالي فإن الطبقات أعلاه (عرض تقديمي عادة) سيتم عزلها بواسطة هذه التجريدات. ضمن النموذج ، قد يستخدم تنفيذ خدمة واحدة تجريد أخرى. يضيف التحسين الإضافي قواعد إلى من يحمل إشارات إلى الكيانات أو الكائنات القيمة التي تفرض سياق دورة حياة أكثر رسمية. لمزيد من المعلومات حول هذا ، أوصي بدراسة كتاب إريك إيفانز أو تصميم مدفوع المجال بسرعة.

إذا كانت تقنية ORM الخاصة بك تتعامل فقط مع كائنات DTO بشكل جيد ، فهذا لا يعني أنه يجب عليك التخلص من كائنات كيان غنية. لا يزال بإمكانك لف كائنات DTO الخاصة بك مع كائنات الكيان:

public class MonkeyData
{
   public string Name { get; set; }
   public List<Food> FavoriteFood { get; set; }
}

public interface IMonkeyRepository
{
   Monkey GetMonkey(string name) // fetches DTO, uses entity constructor
   void SaveMonkey(Monkey monkey) // uses entity GetData(), stores DTO
}


public class Monkey
{
   private readonly MonkeyData monkeyData;

   public Monkey(MonkeyData monkeyData)
   {
      this.monkeyData = monkeyData;
   }

   public Name { get { return this.monkeyData.Name; } }

   public bool IsYummy(Food food)
   {
      return this.monkeyData.FavoriteFood.Contains(food);
   }

   public MonkeyData GetData()
   {
      // CLONE the DTO here to avoid giving write access to the
      // entity innards without business rule enforcement
      return CloneData(this.monkeyData);
   }

}

لقد وجدت كتاب Dino Esposito الجديد Microsoft® .NET: تطبيقات الهندسة المعمارية للمؤسسة أن تكون مستودعًا رائعًا للمعرفة لهذه الأنواع من الأسئلة والقضايا.

طبقة الخدمة.

إذا كنت ترغب في إضافة القليل من السلوك إلى كياناتك ، ولكن لا يمكنك تعديل كياناتك ، فاحرص على تجربة طرق التمديد. سأفعل هذا فقط لسيناريوهات بسيطة كما في مثالك. أي شيء أكثر تعقيدًا أو ينسق بين العديد من الكيانات و/أو الخدمات أو الطبقات أو أي شيء يجب أن يكون في خدمة المجال كما هو مقترح بالفعل.

على سبيل المثال (من الأمثلة الخاصة بك):

public static class LineItemEntityExtensions
{
  public static decimal CalculateTotal(this LineItemEntity li)
  {
    return li.Quantity * li.Price;
  }
}

public static class OrderEntityExtensions
{
  public static decimal CalculateOrderTotal(this OrderEntity order)
  {
    decimal orderTotal = 0;
    foreach (LineItemEntity li in order.LineItems)
      orderTotal += li.CalculateTotal();
    return orderTotal;
  }
}

public class SomewhereElse
{
  public void DoSomething(OrderEntity order)
  {
    decimal total = order.CalculateOrderTotal();
    ...
  }
}

إذا كان هناك عدد قليل جدًا من هذه الإضافات التي تريدها ، فيمكنك فقط وضعها جميعًا في فئة "Domainextensions" ، لكنني أقترح معاملتها باحترام منتظم والحفاظ على جميع امتدادات الكيان في فصل واحد في ملفها الخاص .

لمعلوماتك: المرة الوحيدة التي قمت فيها بذلك هي عندما كان لدي حل L2S ولم أرغب في العبث بالجزارات. لم يكن لدي الكثير من الامتدادات لأن الحل كان صغيرًا. أحب فكرة الذهاب مع طبقة خدمات المجال الكاملة بشكل أفضل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top