Jak komunikować komunikaty/błędy warstwy usług na wyższych warstwach za pomocą programu MVP?

Obecnie piszę ASP.Net aplikacja z poziomu interfejsu użytkownika w dół. Implementuję architekturę MVP, ponieważ mam dość Winforms i chciałem czegoś, co lepiej rozdzielałoby obawy.

Więc z MVP, prezenter obsługuje zdarzenia podnoszone przez Widok. Oto jakiś kod, który mam do czynienia z tworzeniem użytkowników:

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" });
        }
    }
}

Mam swoją główną walidację formularza wykonaną przy użyciu wbudowanych kontrolek walidacji. Net, ale teraz muszę zweryfikować, czy dane są wystarczająco spełnia kryteria warstwy usługowej.

Załóżmy, że mogą pojawić się następujące komunikaty warstwy usług:

  • konto E-mail już istnieje (awaria)
  • referowanie wprowadzonego użytkownika nie istnieje (awaria)
  • długość hasła przekracza dozwoloną długość magazynu danych (awaria)
  • Member created successful (sukces)

Załóżmy również, że w warstwie usług będzie więcej reguł, których interfejs nie może przewidzieć.

Obecnie mam warstwa usług rzuca wyjątek, jeśli nie poszło zgodnie z planem. Czy to wystarczająca strategia? Czy ten kod śmierdzi wam? Gdybym napisał taką warstwę serwisową to byś się zirytował z konieczności pisania prezenterów, którzy używają jej w ten sposób? Kody zwrotne wydają się zbyt stare, a bool nie jest wystarczająco pouczający.


Edit not by OP: comments that was posted as answers by the OP


Cheekysoft, I like the pojęcie usługi. Mam już globalny moduł wyjątków dla WYJĄTKÓW, których nie przewiduję. Czy robienie tych wszystkich niestandardowych wyjątków jest dla ciebie żmudne? Myślałem, że catching Base Exception class jest trochę śmierdzący, ale nie byłem dokładnie pewien, jak postęp stamtąd.

Tgmdbm, podoba mi się sprytne użycie wyrażenia lambda!

Dzięki Cheekysoft za kontynuację. Więc zgaduję, że to będzie strategia, jeśli nie masz nic przeciwko, że użytkownik jest wyświetla osobną stronę (jestem przede wszystkim web developerem), jeśli wyjątek nie jest obsługiwany.

Jeśli jednak chcę zwrócić komunikat o błędzie w tym samym widoku, w którym użytkownik przesłał dane, które spowodowały błąd, będę musiał złapać wyjątek w prezenterze?

Oto jak wygląda CreateUserView, gdy prezenter obsłużył ServiceLayerException:

Utwórz użytkownika

W przypadku tego rodzaju błędu, miło jest zgłosić go w tym samym widoku.

W każdym razie, myślę, że wykraczamy poza zakres mojego pierwotnego pytania. Pobawię się tym, co napisałeś i jeśli będę potrzebował dalszych szczegółów, zamieszczę nowe pytanie.
Author: Pops, 2008-08-22

3 answers

To mi pasuje. Wyjątki są preferowane, ponieważ mogą być wyrzucane na górę warstwy usługi z dowolnego miejsca wewnątrz warstwy usługi, bez względu na to, jak głęboko zagnieżdżona jest implementacja metody usługi. Dzięki temu kod serwisowy jest czysty, ponieważ wiesz, że prezenter wywołujący zawsze otrzyma powiadomienie o problemie.

Don ' t catch Exception

Jednak nie łap WYJĄTKÓW w prezenterze, wiem, że to kuszące, bo trzyma kod jest krótszy, ale musisz wyłapać konkretne wyjątki, aby uniknąć wyłapywania WYJĄTKÓW na poziomie systemowym.

Zaplanuj prostą hierarchię WYJĄTKÓW

Jeśli zamierzasz używać WYJĄTKÓW w ten sposób, powinieneś zaprojektować hierarchię wyjątków dla własnych klas WYJĄTKÓW. Na chwilę Utwórz klasę ServiceLayerException i wrzuć jedną z nich do swoich metod serwisowych, gdy wystąpi problem. Następnie, jeśli chcesz rzucić wyjątek, który powinien / mógłby być obsługiwany inaczej przez prezenter, możesz rzucić określoną podklasę ServiceLayerException: powiedzmy, AccountAlreadyExistsException.

Twój prezenter ma wtedy możliwość wykonania

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.

Używanie dziedziczenia we własnych klasach WYJĄTKÓW oznacza, że nie jesteś zobowiązany do przechwytywania wielokrotnych WYJĄTKÓW w swoim prezenterze - możesz, jeśli jest taka potrzeba-i nie kończy się przypadkowym przechwytywaniem WYJĄTKÓW, z którymi nie możesz sobie poradzić. Jeśli prezenter znajduje się już na szczycie stosu połączeń, dodaj catch (wyjątek ) blok do obsługi błędów systemowych z innym widokiem.

Zawsze staram się myśleć o mojej warstwie usług jako o oddzielnej bibliotece dystrybuowalnej i rzucać wyjątek tak specyficzny, jak ma to sens. Następnie do prezentera/kontrolera / zdalnej implementacji usługi należy decyzja, czy musi martwić się o konkretne szczegóły, czy po prostu traktować problemy jako ogólny błąd.

 15
Author: Cheekysoft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:27:42

Jak sugeruje Cheekysoft, chciałbym przenieść wszystkie główne wyjątki do ExceptionHandler i pozwolić, aby te wyjątki bańka up. ExceptionHandler renderuje odpowiedni widok dla typu wyjątku.

Wszelkie wyjątki walidacji powinny być jednak obsługiwane w widoku, ale zazwyczaj ta logika jest wspólna dla wielu części aplikacji. Więc lubię mieć takiego pomocnika

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;
    }
}

Następnie podczas wywoływania usługi po prostu wykonaj następujące czynności

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

Następnie w błędy są puste, możesz iść.

Możesz pójść dalej i rozszerzyć go za pomocą custome exception handlers, które obsługują uncommon wyjątki.

 3
Author: tgmdbm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-22 14:57:41

W odpowiedzi na kolejne pytanie:

Co do tworzenia wyjątków, które stają się nudne, to można się do tego przyzwyczaić. Użycie dobrego generatora kodu lub szablonu może stworzyć klasę wyjątku z minimalną edycją ręczną w ciągu około 5 lub 10 sekund.

Jednak w wielu rzeczywistych aplikacjach obsługa błędów może stanowić 70% pracy, więc to wszystko jest tylko częścią gry.

Jak sugeruje tgmdbm, w aplikacjach MVC/MVP pozwalam wszystkim moim niewygodnym wyjątkom bąbelkować aż do top i daj się złapać dyspozytorowi, który deleguje do wyjątkowego sprzedawcy. Skonfigurowałem go tak, że używa ExceptionResolver, który wygląda w pliku konfiguracyjnym, aby wybrać odpowiedni widok, aby pokazać użytkownikowi. Java Spring MVC library robi to bardzo dobrze. Oto fragment pliku konfiguracyjnego dla Resolvera WYJĄTKÓW Spring MVC-its dla Java / Spring, ale wpadniesz na pomysł.

To wymaga ogromnej ilości obsługi wyjątków od prezenterów/kontrolerów w ogóle.

<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>
 1
Author: Cheekysoft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-24 13:25:28