【问题标题】:Difference between service layer and controller in practice实践中服务层和控制器的区别
【发布时间】:2016-12-05 07:07:22
【问题描述】:

我已经阅读了很多关于服务层和控制器之间差异的理论,对于如何在实践中实现这一点我有一些疑问。 Service layer and controller: who takes care of what? 的一个回答说:

我试图限制控制器做与验证 http 相关的工作 参数,决定使用什么参数调用什么服务方法, 在 httpsession 或请求中放入什么,重定向什么视图或 转发或类似的网络相关内容。

来自http://www.bennadel.com/blog/2379-a-better-understanding-of-mvc-model-view-controller-thanks-to-steven-neiland.htm:

危险信号:如果出现以下情况,我的控制器架构可能会变坏:

Controller 向 Service 层发出过多请求。这 控制器向服务层发出许多请求 返回数据。控制器向服务层发出请求 不传入参数。

目前我正在使用 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())放在这个controller的超类中,这个方法放在对了,还是必须放在service层??

请帮忙,这对我来说真的很重要。

【问题讨论】:

  • 你应该忘记“控制器向服务层发出太多请求。控制器向服务层发出大量请求,但没有返回数据。控制器向服务层发出请求没有传递参数。”对我来说听起来像无意义
  • “请显示此方法的更好版本?”,试试 codereview.stackexchange.com

标签: java spring-mvc model-view-controller


【解决方案1】:

Spring Controller 通常与 Spring API(使用 Model、ModelAndView...等类)或 Servlet API(HttpServletRequest、HttpServletResponse...)相关联。方法可以返回String 解析为模板名称(JSP...)的结果。 Controller 肯定偏向于 Web GUI,强烈依赖 Web 技术。

Services 另一方面应该在设计时考虑到业务逻辑,而不是假设客户。我们可以远程服务、将其公开为 Web 服务、实现 Web 前端或 Swing 客户端。 Service 不应该依赖于 Spring MVC、Servlet API 等。这样,如果您需要重新定位您的应用程序,您可以重用大部分业务逻辑。

关于从控制器层对服务层的过多调用的注释,主要是性能问题,恕我直言,这是不同的。如果对服务层的每次调用都查询数据库,您可能会遇到性能问题。如果服务层和控制器层不在同一个 JVM 中运行,您也可能会遇到性能问题。这是设计应用程序的另一个非常重要的方面,但它表明您应该对服务调用进行外观处理,以便为控制器层提供更粗粒度的操作。

【讨论】:

    【解决方案2】:

    在这两个示例中,为什么需要转换 Client?那是代码的味道。

    由于对服务层的调用也是建立数据库事务边界的调用,所以多次调用意味着它们是在不同的事务中执行的,因此不一定相互一致。

    这就是不鼓励多次调用的原因之一。 @ArthurNoseda 在his answer 中提到了其他充分的理由。

    在您的第一种情况下,应该对服务层进行一次调用,例如像这样:

    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 相关的操作保留在它们所属的控制器中。

    【讨论】:

    • 非常感谢例如)我已经转换为客户端,因为在我的网络应用程序中,我有用户类,以及两个子类 Client 和 Translator,并且我有抽象类 UserService - 它有一些常用方法User 和 Translator,因为这个方法放在 ClientController 中(但不在 TranslatorController 中),所以我可以转换为 Client。如果不好,请告诉我更好的方法:)
    • @Yuriy service 如何知道是为给定用户返回Client 还是Translator?您应该有两种服务方法,每种类型一种。
    • 所以,我要创建通用接口 UserService 和两个子类 ClientService 和 TranslatorService。谢谢:)
    • 还有一个问题。我可以有一个方法,因为控制器从 Principal 对象获取电子邮件,转换取决于控制器类型,并且我为此方法编写了适当的文档吗?编辑我的第一条评论:我有抽象类 UserService - 它对客户端和翻译器都有一些通用方法.造成这种情况的原因是两个类中都存在很多相似的方法,并且为每个这样的方法创建了两个不同的实现 - 这是一个重复的代码
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-14
    • 2011-04-19
    • 1970-01-01
    • 1970-01-01
    • 2012-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多