MVP を使用してサービス層のメッセージ/エラーを上位層にどのように伝達しますか?
-
09-06-2019 - |
質問
私は現在、UI から ASP.Net アプリを作成しています。私が MVP アーキテクチャを実装しているのは、Winforms にうんざりしていて、関心をより適切に分離できるものが欲しかったからです。
したがって、MVP では、プレゼンターはビューによって発生したイベントを処理します。ユーザーの作成を処理するために用意したコードをいくつか示します。
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 が予期できないより多くのルールがサービス層に存在するとします。
現在、計画どおりに進まない場合にサービス層に例外をスローさせるようにしています。それは十分な戦略ですか?皆さんはこのコードに匂いを感じますか?私がこのようなサービス レイヤーを作成した場合、それをこのように使用するプレゼンターを作成しなければならないことにイライラしますか?戻りコードは古すぎるように思えますし、ブール値では十分な情報が得られません。
OP ではなく編集します:OP によって回答として投稿されたフォローアップ コメントを統合する
Cheekysoft さん、私は ServiceLayerException の概念が好きです。予期しない例外に対応するグローバル例外モジュールがすでにあります。これらすべてのカスタム例外を作成するのは面倒だと思いますか?基本の Exception クラスをキャッチするのは少し臭いと思っていましたが、そこからどのように進歩するかは正確にはわかりませんでした。
tgmdbm さん、ラムダ式の賢い使い方が気に入っています。
Cheekysoft さん、フォローありがとうございます。したがって、例外が処理されない場合にユーザーに別のページが表示されることを気にしない場合(私は主にWeb開発者です)、それが戦略になると思います。
ただし、ユーザーがエラーの原因となったデータを送信したのと同じビューでエラー メッセージを返したい場合は、Presenter で Exception をキャッチする必要があります。
Presenter が ServiceLayerException を処理したときの CreateUserView は次のようになります。
この種のエラーの場合は、同じビューに報告すると便利です。
とにかく、最初の質問の範囲を超えていると思います。あなたが投稿したものを試してみて、さらに詳細が必要な場合は、新しい質問を投稿します。
解決
それは私にとってまさに正しいように思えます。例外は、サービス メソッド実装内でどれほど深くネストされているかに関係なく、サービス層内のどこからでもサービス層の最上位にスローできるため、例外が推奨されます。これにより、呼び出し元のプレゼンターが常に問題の通知を受け取ることがわかるため、サービス コードがクリーンな状態に保たれます。
例外をキャッチしない
しかし、 例外をキャッチしない プレゼンターでは、コードを短くできるので魅力的であることはわかりますが、システム レベルの例外をキャッチしないように、特定の例外をキャッチする必要があります。
単純な例外階層を計画する
この方法で例外を使用する場合は、独自の例外クラスの例外階層を設計する必要があります。少なくとも ServiceLayerException クラスを作成し、問題が発生したときにこれらのクラスの 1 つをサービス メソッドにスローします。次に、プレゼンターによって別の方法で処理されるべき/可能な例外をスローする必要がある場合は、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>