web-dev-qa-db-ja.com

実際のサービス層とコントローラーの違い

サービスレイヤーとコントローラーの違いに関する多くの理論を読みましたが、実際にこれを実現する方法についていくつか質問があります。 サービスレイヤーとコントローラー:誰が何をするのか? の答えは次のとおりです。

私は、コントローラーをhttpパラメーターの検証に関連する作業に制限し、どのパラメーターでどのサービスメソッドを呼び出すか、httpsessionまたはリクエストに何を入れるか、どのビューにリダイレクトまたは転送するか、または同様のWeb関連のものを決定します。

および http://www.bennadel.com/blog/2379-a-better-understanding-of-mvc-model-view-controller-thanks-to-steven-neiland.htm から:

レッドフラグ:次の場合、コントローラーアーキテクチャーが正常に機能しない可能性があります。

コントローラーがサービスレイヤーに要求を大量に送信します。コントローラーは、データを返さないサービスレイヤーにいくつかの要求を行います。コントローラーは、引数を渡さずにサービスレイヤーにリクエストを送信します。

現時点では、Spring MVCを使用してWebアプリを開発しており、変更されたユーザーのメールを保存するための方法があります。

/**
     * <p>If no errors exist, current password is right and new email is unique,
     * updates user's email and redirects to {@link #profile(Principal)}
     */
    @RequestMapping(value = "/saveEmail",method = RequestMethod.POST)
    public ModelAndView saveEmail(
            @Valid @ModelAttribute("changeEmailBean") ChangeEmailBean changeEmailBean,
            BindingResult changeEmailResult,
            Principal user,
            HttpServletRequest request){

        if(changeEmailResult.hasErrors()){
            ModelAndView model = new ModelAndView("/client/editEmail");
            return model;
        }
        final String oldEmail = user.getName();
        Client client = (Client) clientService.getUserByEmail(oldEmail);
        if(!clientService.isPasswordRight(changeEmailBean.getCurrentPassword(), 
                                          client.getPassword())){
            ModelAndView model = new ModelAndView("/client/editEmail");
            model.addObject("wrongPassword","Password doesn't match to real");
            return model;
        }
        final String newEmail = changeEmailBean.getNewEmail();
        if(clientService.isEmailChanged(oldEmail, newEmail)){
            if(clientService.isEmailUnique(newEmail)){
                clientService.editUserEmail(oldEmail, newEmail);
                refreshUsername(newEmail);
                ModelAndView profile = new ModelAndView("redirect:/client/profile");
                return profile;
            }else{
                ModelAndView model = new ModelAndView("/client/editEmail");
                model.addObject("email", oldEmail);
                model.addObject("emailExists","Such email is registered in system already");
                return model;
            }
        }
        ModelAndView profile = new ModelAndView("redirect:/client/profile");
        return profile;
    }

サービスレイヤーへのリクエストが多く、コントローラーからリダイレクトしていることがわかります。これがビジネスロジックです。このメソッドのより良いバージョンを表示してください。

そして別の例。ユーザーのプロファイルを返すこのメソッドがあります。

/**
     * Returns {@link ModelAndView} client's profile
     * @param user - principal, from whom we get {@code Client}
     * @throws UnsupportedEncodingException
     */
    @RequestMapping(value = "/profile", method = RequestMethod.GET)
    public ModelAndView profile(Principal user) throws UnsupportedEncodingException{
        Client clientFromDB = (Client)clientService.getUserByEmail(user.getName());
        ModelAndView model = new ModelAndView("/client/profile");
        model.addObject("client", clientFromDB);
        if(clientFromDB.getAvatar() != null){
            model.addObject("image", convertAvaForRendering(clientFromDB.getAvatar()));
        }
        return model;
    }

メソッドconvertAvaForRendering(clientFromDB.getAvatar())は、このコントローラーのスーパークラスに配置されます。これは、このメソッドの正しい配置です。または、サービスレイヤーに配置する必要がありますか?

助けてください、それは私にとって本当に重要です。

12
Yuriy

どちらの例でも、なぜClientをキャストする必要があるのですか?それはコードのにおいです。

サービス層への呼び出しは、データベーストランザクション境界を確立する呼び出しでもあるため、複数の呼び出しを行うことは、それらが異なるトランザクションで実行されることを意味し、必ずしも相互に一貫しているとは限りません。

これが、複数の呼び出しが推奨されない理由の1つです。 @ArthurNosedaは 彼の答え で他の正当な理由に言及しています。

最初のケースでは、サービス層への単一の呼び出しがあるはずです。このようなもの:

if (changeEmailResult.hasErrors()) {
    return new ModelAndView("/client/editEmail");
}
try {
    clientService.updateUserEmail(user.getName(),
                                  changeEmailBean.getCurrentPassword(),
                                  changeEmailBean.getNewEmail());
} catch (InvalidPasswordException unused) {
    ModelAndView model = new ModelAndView("/client/editEmail");
    model.addObject("wrongPassword", "Password doesn't match to real");
    return model;
} catch (DuplicateEmailException unused) {
    ModelAndView model = new ModelAndView("/client/editEmail");
    model.addObject("email", oldEmail);
    model.addObject("emailExists", "Such email is registered in system already");
    return model;
}
refreshUsername(newEmail);
return new ModelAndView("redirect:/client/profile");

例外の代わりに戻り値を使用することもできます。

ご覧のとおり、これにより、メールがサービス層に変更されるビジネスロジックが委任され、UIに関連するすべてのアクションは、それらが属するコントローラーに保持されます。

7
Andreas

Spring Controllerは通常、Spring API(ModelModelAndView...などのクラスを使用)またはサーブレットAPI(HttpServletRequestHttpServletResponse...)。メソッドは、テンプレートの名前(JSP ...)に解決されるString結果を返すことができます。 Controllerは、Webテクノロジーに強く依存しているため、Web GUIに偏っています。

一方、Services shouldビジネスロジックを念頭に置いて設計し、クライアントについての前提はありません。サービスをリモート化し、Webサービスとして公開し、WebフロントエンドまたはSwingクライアントを実装することができます。 A Serviceshould Spring MVC、サーブレットAPIなどには依存しません。これにより、アプリケーションのターゲットを変更する必要がある場合、ビジネスロジックのほとんどを再利用できます。

コントローラレイヤからサービスレイヤへの呼び出しが多すぎるという注意事項については、ほとんどがパフォーマンスの問題であり、IMHOは別のものです。サービスレイヤーへの各呼び出しがデータベースをクエリする場合、パフォーマンスの問題が発生する可能性があります。サービス層とコントローラー層が同じJVMで実行されていない場合、パフォーマンスの問題が発生する可能性があります。これは、アプリケーション設計のもう1つの非常に重要な側面ですが、コントローラーレイヤーに粗い操作を提供するには、サービス呼び出しをファサードにする必要があることを示しています。

12
Arthur Noseda