Pergunta

Atualmente estou escrevendo um aplicativo ASP.Net da interface do usuário para baixo.Estou implementando uma arquitetura MVP porque estou farto de Winforms e queria algo que tivesse uma melhor separação de interesses.

Assim, com o MVP, o Presenter lida com eventos gerados pela View.Aqui está um código que tenho para lidar com a criação de usuários:

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

Fiz a validação do meu formulário principal usando os controles de validação .Net integrados, mas agora preciso verificar se os dados satisfazem suficientemente os critérios da camada de serviço.

Digamos que as seguintes mensagens da camada de serviço possam aparecer:

  • A conta de e-mail já existe (falha)
  • O usuário de referência inserido não existe (falha)
  • O comprimento da senha excede o comprimento permitido do armazenamento de dados (falha)
  • Membro criado com sucesso (sucesso)

Digamos também que haverá mais regras na camada de serviço que a UI não pode prever.

Atualmente estou fazendo com que a camada de serviço lance uma exceção se as coisas não correrem conforme planejado.Essa é uma estratégia suficiente?Esse código cheira para vocês?Se eu escrevesse uma camada de serviço como essa, você ficaria irritado por ter que escrever apresentadores que a usassem dessa maneira?Os códigos de retorno parecem muito antigos e um bool simplesmente não é informativo o suficiente.


Editar não por OP:mesclando comentários de acompanhamento que foram postados como respostas pelo OP


Cheekysoft, gosto do conceito de ServiceLayerException.Já tenho um módulo de exceção global para as exceções que não prevejo.Você acha tedioso tornar todas essas exceções personalizadas?Eu estava pensando que capturar a classe Exception base era um pouco fedorento, mas não tinha certeza de como progredir a partir daí.

tgmdbm, gosto do uso inteligente da expressão lambda aqui!


Obrigado Cheekysoft pelo acompanhamento.Então, suponho que essa seria a estratégia se você não se importa que o usuário exiba uma página separada (sou principalmente um desenvolvedor web) se a exceção não for tratada.

Porém, se eu quiser retornar a mensagem de erro na mesma view onde o usuário enviou os dados que causaram o erro, eu teria então que capturar a Exception no Presenter?

Esta é a aparência de CreateUserView quando o Presenter manipula ServiceLayerException:

Create a user

Para esse tipo de erro, é bom reportá-lo à mesma visualização.

De qualquer forma, acho que agora estamos indo além do escopo da minha pergunta original.Vou brincar com o que você postou e se precisar de mais detalhes postarei uma nova pergunta.

Foi útil?

Solução

Isso parece certo para mim.As exceções são preferíveis, pois podem ser lançadas no topo da camada de serviço de qualquer lugar dentro da camada de serviço, não importa quão profundamente aninhadas estejam dentro da implementação do método de serviço.Isso mantém o código de serviço limpo, pois você sabe que o apresentador chamador sempre receberá uma notificação do problema.

Não pegue exceção

No entanto, não pegue exceção no apresentador, sei que é tentador porque mantém o código mais curto, mas você precisa capturar exceções específicas para evitar capturar as exceções no nível do sistema.

Planeje uma hierarquia de exceções simples

Se você for usar exceções dessa maneira, deverá criar uma hierarquia de exceções para suas próprias classes de exceções.No mínimo, crie uma classe ServiceLayerException e lance uma delas em seus métodos de serviço quando ocorrer um problema.Então, se você precisar lançar uma exceção que deveria/poderia ser tratada de maneira diferente pelo apresentador, você pode lançar uma subclasse específica de ServiceLayerException:digamos, AccountAlreadyExistsException.

Seu apresentador terá então a opção de fazer

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 herança em suas próprias classes de exceção significa que você não é obrigado a capturar exceções múltiplas em seu apresentador - você pode, se necessário - e você não acabará capturando acidentalmente exceções que não pode manipular.Se o seu apresentador já estiver no topo da pilha de chamadas, adicione um bloco catch( Exception ) para tratar os erros do sistema com uma visão diferente.

Eu sempre tento pensar na minha camada de serviço como uma biblioteca distribuível separada e lanço uma exceção tão específica quanto faz sentido.Cabe então à implementação do apresentador/controlador/serviço remoto decidir se precisa se preocupar com detalhes específicos ou apenas tratar os problemas como um erro genérico.

Outras dicas

Como sugere Cheekysoft, eu tenderia a mover todas as exceções principais para um ExceptionHandler e deixar essas exceções surgirem.O ExceptionHandler renderizaria a visualização apropriada para o tipo de exceção.

Quaisquer exceções de validação, entretanto, devem ser tratadas na visualização, mas normalmente essa lógica é comum a muitas partes do seu aplicativo.Então eu gosto de ter um ajudante assim

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

Então, ao ligar para o seu serviço, faça o seguinte

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

Então em erros está vazio, você está pronto para prosseguir.

Você pode levar isso adiante e estendê-lo com manipuladores de exceção personalizados que tratam incomum exceções.

Em resposta à pergunta de acompanhamento:

Quanto a criar exceções se tornando tedioso, você meio que se acostuma.O uso de um bom gerador de código ou modelo pode criar a classe de exceção com edição manual mínima em cerca de 5 ou 10 segundos.

No entanto, em muitas aplicações do mundo real, o tratamento de erros pode representar 70% do trabalho, portanto, na verdade, tudo faz parte do jogo.

Como sugere o tgmdbm, em aplicativos MVC/MVP, deixo todas as minhas exceções incontroláveis ​​subirem ao topo e serem capturadas pelo despachante que delega a um ExceptionHandler.Eu configurei para que ele use um ExceptionResolver que procura no arquivo de configuração para escolher uma visualização apropriada para mostrar ao usuário.A biblioteca Spring MVC do Java faz isso muito bem.Aqui está um trecho de um arquivo de configuração para o resolvedor de exceções do Spring MVC - é para Java/Spring, mas você já entendeu.

Isso exige uma enorme quantidade de tratamento de exceções de seus apresentadores/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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top