MVP를 사용하여 서비스 계층 메시지/오류를 상위 계층에 어떻게 전달합니까?

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

문제

현재 UI 아래에서 ASP.Net 앱을 작성 중입니다.저는 Winforms에 질려 있고 문제를 더 잘 분리할 수 있는 무언가를 원했기 때문에 MVP 아키텍처를 구현하고 있습니다.

따라서 MVP를 사용하면 Presenter가 View에서 발생한 이벤트를 처리합니다.다음은 사용자 생성을 처리하기 위해 마련한 몇 가지 코드입니다.

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

내장된 .Net 유효성 검사 컨트롤을 사용하여 기본 양식 유효성 검사를 수행했지만 이제 데이터가 서비스 계층 기준을 충분히 충족하는지 확인해야 합니다.

다음 서비스 계층 메시지가 나타날 수 있다고 가정해 보겠습니다.

  • 이메일 계정이 이미 존재합니다(실패)
  • 입력한 추천 사용자가 존재하지 않습니다(실패)
  • 암호 길이가 데이터스토어 허용 길이를 초과함(실패)
  • 회원이 성공적으로 생성되었습니다(성공).

또한 UI가 예상할 수 없는 서비스 계층에 더 많은 규칙이 있을 것이라고 가정해 보겠습니다.

현재 계획대로 진행되지 않으면 서비스 계층에서 예외를 발생시키도록 하고 있습니다.그것이 충분한 전략인가?이 코드에서 냄새가 나나요?내가 이와 같은 서비스 레이어를 작성한다면, 이런 방식으로 이를 사용하는 프레젠터를 작성해야 한다는 점에 짜증이 나시겠습니까?반환 코드는 너무 구식인 것 같고 boolean만으로는 충분한 정보를 얻을 수 없습니다.


OP가 아닌 편집 :OP가 답변으로 게시한 후속 댓글에 병합


Cheekysoft, 저는 ServiceLayerException이라는 개념을 좋아합니다.예상하지 못한 예외에 대한 전역 예외 모듈이 이미 있습니다.이러한 모든 사용자 지정 예외를 만드는 것이 지루하다고 생각하시나요?나는 기본 Exception 클래스를 잡는 것이 약간 냄새가 난다고 생각했지만 거기에서 어떻게 진행되는지 정확히 확신하지 못했습니다.

tgmdbm, 나는 거기에서 람다 표현을 영리하게 사용하는 것을 좋아합니다!


후속 조치에 대해 Cheekysoft에게 감사드립니다.따라서 예외가 처리되지 않으면 사용자에게 별도의 페이지가 표시되는 것을 개의치 않는 경우(저는 주로 웹 개발자입니다) 이것이 전략이 될 것이라고 추측합니다.

그러나 사용자가 오류를 일으킨 데이터를 제출한 동일한 보기에서 오류 메시지를 반환하려면 Presenter?에서 예외를 포착해야 합니다.

Presenter가 ServiceLayerException을 처리했을 때 CreateUserView의 모습은 다음과 같습니다.

Create a user

이런 종류의 오류에 대해서는 동일한 보기에 보고하는 것이 좋습니다.

어쨌든, 이제 우리는 원래 질문의 범위를 벗어나고 있는 것 같습니다.게시한 내용을 검토해 보고 추가 세부정보가 필요하면 새 질문을 게시하겠습니다.

도움이 되었습니까?

해결책

나에게는 딱 맞는 것 같다.예외는 서비스 메서드 구현 내부에 얼마나 깊이 중첩되어 있는지에 관계없이 서비스 계층 내부 어디에서나 서비스 계층의 맨 위로 던져질 수 있으므로 선호됩니다.이렇게 하면 호출 발표자가 항상 문제에 대한 알림을 받게 되므로 서비스 코드가 깔끔하게 유지됩니다.

예외를 잡지 마세요

하지만, 예외를 잡지 마세요 프리젠터에서는 코드를 더 짧게 유지하기 때문에 매력적이지만 시스템 수준 예외를 포착하지 않으려면 특정 예외를 포착해야 합니다.

단순 예외 계층 구조 계획

이런 방식으로 예외를 사용하려면 고유한 예외 클래스에 대한 예외 계층 구조를 디자인해야 합니다.최소한 ServiceLayerException 클래스를 생성하고 문제가 발생하면 서비스 메서드에 이들 중 하나를 발생시킵니다.그런 다음 발표자가 다르게 처리해야 하거나 다르게 처리할 수 있는 예외를 발생시켜야 하는 경우 ServiceLayerException의 특정 하위 클래스를 발생시킬 수 있습니다.예를 들어 AccountAlreadyExistsException이 있습니다.

그러면 발표자는 다음을 수행할 수 있습니다.

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.

자신의 예외 클래스에서 상속을 사용한다는 것은 프리젠터에서 여러 예외를 잡을 필요가 없다는 것을 의미합니다. 필요하다면 잡을 수 있습니다. 또한 처리할 수 없는 예외를 실수로 잡는 일도 없습니다.발표자가 이미 호출 스택의 맨 위에 있는 경우 catch( Exception ) 블록을 추가하여 다른 보기로 시스템 오류를 처리하세요.

나는 항상 내 서비스 계층을 별도의 배포 가능한 라이브러리로 생각하려고 노력하고, 가능한 한 구체적인 예외를 던집니다.그런 다음 특정 세부 사항에 대해 걱정해야 하는지 아니면 문제를 일반적인 오류로 처리해야 하는지 결정하는 것은 발표자/컨트롤러/원격 서비스 구현에 달려 있습니다.

다른 팁

Cheekysoft가 제안한 것처럼 저는 모든 주요 예외를 ExceptionHandler로 옮기고 해당 예외가 버블링되도록 하는 경향이 있습니다.ExceptionHandler는 예외 유형에 적합한 뷰를 렌더링합니다.

그러나 모든 유효성 검사 예외는 뷰에서 처리되어야 하지만 일반적으로 이 논리는 애플리케이션의 여러 부분에 공통됩니다.그래서 난 이런 도우미가 있는 걸 좋아해

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

그런 다음 서비스를 호출할 때 다음을 수행하세요.

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

그런 다음 오류가 비어 있으면 가셔도 좋습니다.

이를 더 발전시켜 처리하는 맞춤형 예외 처리기를 사용하여 확장할 수 있습니다. 드문 예외.

후속 질문에 대한 답변:

예외를 만드는 것이 지루해지는 것에 대해서는 어느 정도 익숙해졌습니다.좋은 코드 생성기 또는 템플릿을 사용하면 약 5~10초 내에 최소한의 손 편집만으로 예외 클래스를 생성할 수 있습니다.

그러나 많은 실제 응용 프로그램에서는 오류 처리가 작업의 70%를 차지할 수 있으므로 이는 모두 게임의 일부일 뿐입니다.

tgmdbm이 제안하는 것처럼 MVC/MVP 애플리케이션에서는 처리할 수 없는 모든 예외가 맨 위로 올라가도록 하고 ExceptionHandler에 위임하는 디스패처에 의해 포착됩니다.사용자에게 표시할 적절한 보기를 선택하기 위해 구성 파일을 살펴보는 ExceptionResolver를 사용하도록 설정했습니다.Java의 Spring MVC 라이브러리는 이를 매우 잘 수행합니다.다음은 Spring MVC의 예외 해결 프로그램에 대한 구성 파일의 일부입니다. 이는 Java/Spring용이지만 아이디어를 얻을 수 있습니다.

이를 위해서는 프리젠터/컨트롤러 전체에서 엄청난 양의 예외 처리가 필요합니다.

<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>
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top