كيف يمكنك توصيل رسائل/أخطاء طبقة الخدمة إلى الطبقات العليا باستخدام MVP؟

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

سؤال

أقوم حاليًا بكتابة تطبيق ASP.Net من واجهة المستخدم إلى الأسفل.أقوم بتنفيذ بنية MVP لأنني سئمت من Winforms وأردت شيئًا يحتوي على فصل أفضل بين الاهتمامات.

لذا، باستخدام MVP، يتعامل المقدم مع الأحداث التي يثيرها العرض.إليك بعض التعليمات البرمجية التي أستخدمها للتعامل مع إنشاء المستخدمين:

public class CreateMemberPresenter
{
    private ICreateMemberView view;
    private IMemberTasks tasks;

    public CreateMemberPresenter(ICreateMemberView view) 
        : this(view, new StubMemberTasks())
    {
    }

    public CreateMemberPresenter(ICreateMemberView view, IMemberTasks tasks)
    {
        this.view = view;
        this.tasks = tasks;

        HookupEventHandlersTo(view);
    }

    private void HookupEventHandlersTo(ICreateMemberView view)
    {
        view.CreateMember += delegate { CreateMember(); };
    }

    private void CreateMember()
    {
        if (!view.IsValid)
            return;

        try
        {
            int newUserId;
            tasks.CreateMember(view.NewMember, out newUserId);
            view.NewUserCode = newUserId;
            view.Notify(new NotificationDTO() { Type = NotificationType.Success });
        }
        catch(Exception e)
        {
            this.LogA().Message(string.Format("Error Creating User: {0}", e.Message));
            view.Notify(new NotificationDTO() { Type = NotificationType.Failure, Message = "There was an error creating a new member" });
        }
    }
}

لقد قمت بالتحقق من صحة النموذج الرئيسي الخاص بي باستخدام عناصر التحكم في التحقق من الصحة المضمنة في .Net، ولكنني الآن بحاجة إلى التحقق من أن البيانات تفي بشكل كافٍ بمعايير طبقة الخدمة.

لنفترض أن رسائل طبقة الخدمة التالية يمكن أن تظهر:

  • حساب البريد الإلكتروني موجود بالفعل (فشل)
  • إحالة المستخدم الذي تم إدخاله غير موجود (فشل)
  • يتجاوز طول كلمة المرور الطول المسموح به لمخزن البيانات (فشل)
  • تم إنشاء العضو بنجاح (نجاح)

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

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


تحرير ليس عن طريق OP:الدمج في تعليقات المتابعة التي تم نشرها كإجابات بواسطة OP


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

tgmdbm، أحب الاستخدام الذكي لتعبير لامدا هناك!


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

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

إليك ما يبدو عليه CreateUserView عندما يتعامل مقدم العرض مع ServiceLayerException:

Create a user

بالنسبة لهذا النوع من الأخطاء، من الجيد الإبلاغ عنه لنفس العرض.

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

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

المحلول

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

لا تلتقط الاستثناء

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

التخطيط لتسلسل هرمي بسيط للاستثناءات

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

ثم يكون لدى مقدم العرض الخاص بك خيار القيام بذلك

try {
  // call service etc.
  // handle success to view
} 
catch (AccountAlreadyExistsException) {
  // set the message and some other unique data in the view
}
catch (ServiceLayerException) {
  // set the message in the view
}
// system exceptions, and unrecoverable exceptions are allowed to bubble 
// up the call stack so a general error can be shown to the user, rather 
// than showing the form again.

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

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

نصائح أخرى

كما يقترح Cheekysoft، سأميل إلى نقل جميع الاستثناءات الرئيسية إلى ExceptionHandler والسماح لهذه الاستثناءات بالظهور.سيقدم ExceptionHandler العرض المناسب لنوع الاستثناء.

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

public static class Try {
    public static List<string> This( Action action ) {
      var errors = new List<string>();
      try {
        action();
      }
      catch ( SpecificException e ) {
        errors.Add( "Something went 'orribly wrong" );
      }
      catch ( ... )
      // ...
     return errors;
    }
}

ثم عند الاتصال بالخدمة الخاصة بك فقط قم بما يلي

var errors = Try.This( () => {
  // call your service here
  tasks.CreateMember( ... );
} );

ثم في الأخطاء فارغة، أنت على ما يرام.

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

رداً على السؤال التالي:

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

ومع ذلك، في العديد من تطبيقات العالم الحقيقي، يمكن أن تمثل معالجة الأخطاء 70% من العمل، لذا فهي مجرد جزء من اللعبة حقًا.

كما يقترح tgmdbm، في تطبيقات MVC/MVP، أترك جميع الاستثناءات غير القابلة للمعالجة تتصاعد إلى الأعلى ويتم اكتشافها بواسطة المرسل الذي يفوض إلى ExceptionHandler.لقد قمت بإعداده بحيث يستخدم ExceptionResolver الذي يبحث في ملف التكوين لاختيار طريقة عرض مناسبة لإظهارها للمستخدم.تقوم مكتبة Java Spring MVC بهذا بشكل جيد جدًا.فيما يلي مقتطف من ملف التكوين لمحلل الاستثناءات الخاص بـ Spring MVC - وهو خاص بـ Java/Spring ولكنك ستحصل على الفكرة.

يتطلب هذا قدرًا كبيرًا من معالجة الاستثناءات من مقدمي العرض/وحدات التحكم لديك تمامًا.

<bean id="exceptionResolver"
      class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

  <property name="exceptionMappings">
    <props>
      <prop key="UserNotFoundException">
        rescues/UserNotFound
      </prop>
      <prop key="HibernateJdbcException">
        rescues/databaseProblem
      </prop>
      <prop key="java.net.ConnectException">
        rescues/networkTimeout
      </prop>
      <prop key="ValidationException">
        rescues/validationError
      </prop>
      <prop key="EnvironmentNotConfiguredException">
        rescues/environmentNotConfigured
      </prop>
      <prop key="MessageRejectedPleaseRetryException">
        rescues/messageRejected
      </prop>
    </props>
  </property>
  <property name="defaultErrorView" value="rescues/general" />
</bean>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top