Pregunta

Actualmente estoy escribiendo una aplicación ASP.Net desde la interfaz de usuario hacia abajo.Estoy implementando una arquitectura MVP porque estoy harto de Winforms y quería algo que tuviera una mejor separación de preocupaciones.

Entonces, con MVP, el Presentador maneja los eventos generados por la Vista.Aquí hay un código que tengo implementado para lidiar con la creación de usuarios:

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

Realicé la validación de mi formulario principal utilizando los controles de validación .Net integrados, pero ahora necesito verificar que los datos satisfagan suficientemente los criterios para la capa de servicio.

Supongamos que pueden aparecer los siguientes mensajes de la capa de servicio:

  • La cuenta de correo electrónico ya existe (fallo)
  • El usuario referente ingresado no existe (fallo)
  • La longitud de la contraseña excede la longitud permitida del almacén de datos (fallo)
  • Miembro creado exitosamente (éxito)

Digamos también que habrá más reglas en la capa de servicio que la interfaz de usuario no puede anticipar.

Actualmente, estoy haciendo que la capa de servicio lance una excepción si las cosas no salieron según lo planeado.¿Es esa una estrategia suficiente?¿Les huele este código, muchachos?Si escribiera una capa de servicio como esta, ¿le molestaría tener que escribir presentadores que la usen de esta manera?Los códigos de retorno parecen demasiado antiguos y un bool simplemente no es lo suficientemente informativo.


Editar no por OP:fusionándose en comentarios de seguimiento que fueron publicados como respuestas por el OP


Cheekysoft, me gusta el concepto de ServiceLayerException.Ya tengo un módulo de excepción global para las excepciones que no anticipo.¿Le resulta tedioso hacer todas estas excepciones personalizadas?Estaba pensando que atrapar la clase de Excepción base olía un poco, pero no estaba exactamente seguro de cómo progresar a partir de ahí.

tgmdbm, ¡me gusta el uso inteligente de la expresión lambda allí!


Gracias Cheekysoft por el seguimiento.Entonces, supongo que esa sería la estrategia si no le importa que se muestre al usuario una página separada (soy principalmente un desarrollador web) si no se maneja la excepción.

Sin embargo, si quiero devolver el mensaje de error en la misma vista donde el usuario envió los datos que causaron el error, ¿tendría que detectar la excepción en el presentador?

Así es como se ve CreateUserView cuando el presentador ha manejado ServiceLayerException:

Create a user

Para este tipo de error, es bueno informarlo en la misma vista.

De todos modos, creo que ahora vamos más allá del alcance de mi pregunta original.Jugaré con lo que has publicado y si necesito más detalles publicaré una nueva pregunta.

¿Fue útil?

Solución

Eso me parece perfecto.Las excepciones son preferibles, ya que pueden lanzarse a la parte superior de la capa de servicio desde cualquier lugar dentro de la capa de servicio, sin importar cuán profundamente anidada esté dentro de la implementación del método de servicio.Esto mantiene limpio el código de servicio, ya que sabe que el presentador que llama siempre recibirá una notificación del problema.

No captes la excepción

Sin embargo, no captes la excepción en el presentador, sé que es tentador porque mantiene el código más corto, pero es necesario detectar excepciones específicas para evitar detectar excepciones a nivel del sistema.

Planificar una jerarquía de excepciones simple

Si va a utilizar excepciones de esta manera, debe diseñar una jerarquía de excepciones para sus propias clases de excepción.Como mínimo, cree una clase ServiceLayerException y lance una de estas en sus métodos de servicio cuando ocurra un problema.Luego, si necesita lanzar una excepción que el presentador debería o podría manejar de manera diferente, puede lanzar una subclase específica de ServiceLayerException:digamos, AccountAlreadyExistsException.

Su presentador entonces tiene la opción de hacer

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.

Usar la herencia en sus propias clases de excepción significa que no está obligado a detectar múltiples excepciones en su presentador (puede hacerlo si es necesario) y no terminará detectando accidentalmente excepciones que no puede manejar.Si su presentador ya está en la parte superior de la pila de llamadas, agregue un bloque catch (Excepción) para manejar los errores del sistema con una vista diferente.

Siempre trato de pensar en mi capa de servicio como una biblioteca distribuible separada y lanzo una excepción tan específica como tenga sentido.Luego depende de la implementación del presentador/controlador/servicio remoto decidir si necesita preocuparse por los detalles específicos o simplemente tratar los problemas como un error genérico.

Otros consejos

Como sugiere Cheekysoft, tendería a mover todas las excepciones principales a un ExceptionHandler y dejar que esas excepciones surjan.El ExceptionHandler mostraría la vista adecuada para el tipo de excepción.

Sin embargo, cualquier excepción de validación debe manejarse en la vista, pero normalmente esta lógica es común a muchas partes de su aplicación.Entonces me gusta tener un ayudante como este.

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

Luego, cuando llame a su servicio, simplemente haga lo siguiente

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

Luego, en errores está vacío, listo.

Puede llevar esto más allá y ampliarlo con controladores de excepciones personalizados que manejan poco común excepciones.

En respuesta a la pregunta de seguimiento:

En cuanto a que crear excepciones se vuelva tedioso, uno se acostumbra un poco.El uso de un buen generador de código o plantilla puede crear la clase de excepción con una edición manual mínima en aproximadamente 5 o 10 segundos.

Sin embargo, en muchas aplicaciones del mundo real, el manejo de errores puede representar el 70% del trabajo, por lo que en realidad es solo parte del juego.

Como sugiere tgmdbm, en las aplicaciones MVC/MVP dejo que todas mis excepciones no manejables suban a la cima y sean capturadas por el despachador que delega en un ExceptionHandler.Lo configuré para que use un ExceptionResolver que busca en el archivo de configuración para elegir una vista apropiada para mostrarle al usuario.La biblioteca Spring MVC de Java hace esto muy bien.Aquí hay un fragmento de un archivo de configuración para el solucionador de excepciones de Spring MVC: es para Java/Spring, pero entenderá la idea.

Esto elimina por completo una gran cantidad de manejo de excepciones por parte de sus presentadores/controladores.

<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>
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top