2016-07-30 33 views
3

我已經讀過關於服務層和控制器之間差異的許多理論,並且我在實踐中遇到了一些關於如何實現這一點的問題。一個答案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不通過參數向服務層 發出請求。

目前我正在開發使用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())被放置在超類此控制器,它是這種方法的正確擺放,否則他必須放在服務層??

請幫忙,這對我來說真的很重要。

+0

您應該忘記「Controller向Service層發出太多請求,Controller向Service層發出一些不返回數據的請求,Controller向Service層發出請求而不傳入參數。 「聽起來像*非感*對我 – 2016-07-30 20:08:07

+0

「請顯示更好的版本的這種方法?」,請嘗試codereview.stackexchange.com –

回答

3

在這兩個例子中,爲什麼你需要投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相關的操作保留在其所屬的控制器中。

+0

非常感謝例如)我已投到客戶端,因爲在我的網絡應用程序我有班級用戶,和兩個子類Client和Translator,並且我有抽象類UserService - 它們對User和Translator都有一些常用方法,並且因爲此方法放置在ClientController中(但不在TranslatorController中),所以我可以將其轉換爲Client。如果它不好,請說我更好的方式:) – Yuriy

+0

@Yuriy *服務*如何知道是否爲給定的用戶返回一個'Client'或'Translator'?你應該有兩種服務方法,每種類型一種。 – Andreas

+0

所以,我要創建通用接口UserService和兩個子類ClientService和TranslatorService。謝謝:) – Yuriy

4

一個Spring Controller通常綁在彈簧API(與類似ModelModelAndView ...)或在Servlet API(HttpServletRequestHttpServletResponse ...)。方法可以將String結果返回解析爲模板名稱(JSP ...)。 Controller肯定偏向於Web GUI,並且強烈依賴Web技術。

Service s另一方面應該設計時考慮到業務邏輯,並且沒有關於客戶端的假設。我們可以遠程服務,將其公開爲Web服務,實現Web前端或Swing客戶端。 A Service不依賴於Spring MVC,Servlet API等。這樣,如果您需要重新定位應用程序,則可以重用大部分業務邏輯。

至於關於從控制器層到服務層的調用太多的說明,它主要是性能問題,恕我直言,這是不同的。如果對服務層的每次調用都查詢數據庫,則可能會遇到性能問題。它的服務層和控制器層不在同一個JVM中運行,您也可能遇到性能問題。這是設計應用程序的另一個非常重要的方面,但它會表明您應該調用服務調用以向控制器層提供更粗糙的操作。