Comment communiquer les messages/erreurs de la couche de service aux couches supérieures à l’aide de MVP ?

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

Question

J'écris actuellement une application ASP.Net à partir de l'interface utilisateur.J'implémente une architecture MVP parce que j'en ai marre de Winforms et que je voulais quelque chose qui ait une meilleure séparation des préoccupations.

Ainsi, avec MVP, le Presenter gère les événements déclenchés par la View.Voici un code que j'ai mis en place pour gérer la création d'utilisateurs :

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

La validation de mon formulaire principal est effectuée à l'aide des contrôles de validation .Net intégrés, mais je dois maintenant vérifier que les données satisfont suffisamment aux critères de la couche de service.

Supposons que les messages suivants de la couche de service puissent s'afficher :

  • Le compte e-mail existe déjà (échec)
  • L'utilisateur référent renseigné n'existe pas (échec)
  • La longueur du mot de passe dépasse la longueur autorisée pour la banque de données (échec)
  • Membre créé avec succès (succès)

Disons également qu'il y aura davantage de règles dans la couche de service que l'interface utilisateur ne peut pas anticiper.

Actuellement, la couche de service lève une exception si les choses ne se déroulent pas comme prévu.Est-ce une stratégie suffisante ?Ce code vous sent-il les gars ?Si j'écrivais une couche de service comme celle-ci, seriez-vous ennuyé de devoir écrire des présentateurs qui l'utilisent de cette manière ?Les codes de retour semblent trop old school et un booléen n'est tout simplement pas assez informatif.


Modifier non par OP :fusion dans les commentaires de suivi qui ont été publiés comme réponses par le PO


Cheekysoft, j'aime le concept de ServiceLayerException.J'ai déjà un module d'exception global pour les exceptions que je n'anticipe pas.Trouvez-vous fastidieux de créer toutes ces exceptions personnalisées ?Je pensais qu'attraper la classe d'exception de base était un peu malodorant, mais je ne savais pas exactement comment progresser à partir de là.

tgmdbm, j'aime l'utilisation intelligente de l'expression lambda là-bas !


Merci Cheekysoft pour le suivi.Je suppose donc que ce serait la stratégie si cela ne vous dérange pas que l'utilisateur affiche une page séparée (je suis principalement un développeur Web) si l'exception n'est pas gérée.

Cependant, si je souhaite renvoyer le message d'erreur dans la même vue dans laquelle l'utilisateur a soumis les données à l'origine de l'erreur, je devrais alors intercepter l'exception dans le présentateur ?

Voici à quoi ressemble CreateUserView lorsque le présentateur a géré l'exception ServiceLayerException :

Create a user

Pour ce genre d'erreur, c'est bien de la signaler à la même vue.

Quoi qu'il en soit, je pense que nous dépassons désormais la portée de ma question initiale.Je vais jouer avec ce que vous avez posté et si j'ai besoin de plus de détails, je posterai une nouvelle question.

Était-ce utile?

La solution

Cela me semble parfait.Les exceptions sont préférables car elles peuvent être levées jusqu'au sommet de la couche de service depuis n'importe où à l'intérieur de la couche de service, quelle que soit sa profondeur d'imbrication dans l'implémentation de la méthode de service.Cela permet de conserver le code de service propre, car vous savez que le présentateur appelant sera toujours informé du problème.

N'attrapez pas l'exception

Cependant, ne pas attraper l'exception dans le présentateur, je sais que c'est tentant car cela permet de garder le code plus court, mais vous devez intercepter des exceptions spécifiques pour éviter d'intercepter les exceptions au niveau du système.

Planifier une hiérarchie d'exceptions simple

Si vous envisagez d'utiliser les exceptions de cette manière, vous devez concevoir une hiérarchie d'exceptions pour vos propres classes d'exceptions.Au minimum, créez une classe ServiceLayerException et lancez-en une dans vos méthodes de service lorsqu'un problème survient.Ensuite, si vous devez lever une exception qui devrait/pourrait être gérée différemment par le présentateur, vous pouvez lancer une sous-classe spécifique de ServiceLayerException :disons, AccountAlreadyExistsException.

Votre présentateur a alors la possibilité de faire

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.

L'utilisation de l'héritage dans vos propres classes d'exceptions signifie que vous n'êtes pas obligé d'intercepter plusieurs exceptions dans votre présentateur - vous pouvez le faire si nécessaire - et vous ne finissez pas par intercepter accidentellement des exceptions que vous ne pouvez pas gérer.Si votre présentateur est déjà en haut de la pile d'appels, ajoutez un bloc catch( Exception ) pour gérer les erreurs système avec une vue différente.

J'essaie toujours de considérer ma couche de service comme une bibliothèque distribuable distincte et de lancer une exception aussi spécifique que cela a du sens.Il appartient ensuite à l'implémentation du présentateur/contrôleur/service distant de décider si elle doit se soucier des détails spécifiques ou simplement traiter les problèmes comme une erreur générique.

Autres conseils

Comme le suggère Cheekysoft, j'aurais tendance à déplacer toutes les exceptions majeures dans un ExceptionHandler et à laisser ces exceptions bouillonner.L'ExceptionHandler afficherait la vue appropriée pour le type d'exception.

Toutes les exceptions de validation doivent cependant être gérées dans la vue, mais cette logique est généralement commune à de nombreuses parties de votre application.Donc j'aime avoir une aide comme celle-ci

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

Ensuite, lorsque vous appelez votre service, procédez comme suit

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

Ensuite, dans les erreurs, c'est vide, vous êtes prêt à partir.

Vous pouvez aller plus loin et l'étendre avec des gestionnaires d'exceptions personnalisés qui gèrent rare des exceptions.

En réponse à la question complémentaire :

Quant à créer des exceptions qui deviennent fastidieuses, on s'y habitue un peu.L'utilisation d'un bon générateur de code ou d'un bon modèle peut créer la classe d'exception avec un minimum de modifications manuelles en 5 ou 10 secondes environ.

Cependant, dans de nombreuses applications du monde réel, la gestion des erreurs peut représenter 70 % du travail, ce qui fait donc simplement partie du jeu.

Comme le suggère tgmdbm, dans les applications MVC/MVP, je laisse toutes mes exceptions ingérables remonter vers le haut et être interceptées par le répartiteur qui délègue à un ExceptionHandler.Je l'ai configuré pour qu'il utilise un ExceptionResolver qui recherche dans le fichier de configuration pour choisir une vue appropriée à afficher à l'utilisateur.La bibliothèque Spring MVC de Java le fait très bien.Voici un extrait d'un fichier de configuration pour le résolveur d'exceptions de Spring MVC - c'est pour Java/Spring mais vous aurez l'idée.

Cela enlève une énorme quantité de gestion des exceptions à vos présentateurs/contrôleurs.

<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>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top