How Do You Communicate Service Layer Messages/Errors to Higher Layers Using MVP?

大兔子大兔子 提交于 2019-11-28 17:06:12
Cheekysoft

That sounds just right to me. Exceptions are preferable as they can be thrown up to the top of the service layer from anywhere inside the service layer, no matter how deeply nested inside the service method implementation it is. This keeps the service code clean as you know the calling presenter will always get notification of the problem.

Don't catch Exception

However, don't catch Exception in the presenter, I know its tempting because it keeps the code shorter, but you need to catch specific exceptions to avoid catching the system-level exceptions.

Plan a Simple Exception Hierarchy

If you are going to use exceptions in this way, you should design an exception hierarchy for your own exception classes. At a minumum create a ServiceLayerException class and throw one of these in your service methods when a problem occurs. Then if you need to throw an exception that should/could be handled differently by the presenter, you can throw a specific subclass of ServiceLayerException: say, AccountAlreadyExistsException.

Your presenter then has the option of doing

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.

Using inheritance in your own exception classes means you are not required to catch multipile exceptions in your presenter -- you can if there's a need to -- and you don't end up accidentally catching exceptions you can't handle. If your presenter is already at the top of the call stack, add a catch( Exception ) block to handle the system errors with a different view.

I always try and think of my service layer as a seperate distributable library, and throw as specific an exception as makes sense. It is then up to the presenter/controller/remote-service implementation to decide if it needs to worry about the specific details or just to treat problems as a generic error.

As Cheekysoft suggests, I would tend to move all major exceptions into an ExceptionHandler and let those exceptions bubble up. The ExceptionHandler would render the appropriate view for the type of exception.

Any validation exceptions however should be handled in the view but typically this logic is common to many parts of your application. So I like to have a helper like this

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

Then when calling your service just do the following

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

Then in errors is empty, you're good to go.

You can take this further and extend it with custome exception handlers which handle uncommon exceptions.

In reply to the follow-up question:

As for creating exceptions becoming tedious, you kinda get used to it. Use of a good code generator or template can create the exception class with minimal hand editing within about 5 or 10 seconds.

However, in many real world applications, error handling can be 70% of the work, so it's all just part of the game really.

As tgmdbm suggests, in MVC/MVP applications I let all my unhandlable exceptions bubble up to the top and get caught by the dispatcher which delegates to an ExceptionHandler. I set it up so that it uses an ExceptionResolver that looks in the config file to choose an appropriate view to show the user. Java's Spring MVC library does this very well. Here's a snippet from a config file for Spring MVC's Exception resolver - its for Java/Spring but you'll get the idea.

This takes a huge amount of exception handling out of your presenters/controllers altogether.

<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>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!