2016-07-30 14 views
3

Ich habe viele Theorien über Unterschiede zwischen Service-Schicht und Controller gelesen, und ich habe einige Fragen darüber, wie dies in der Praxis zu realisieren. Eine Antwort auf Service layer and controller: who takes care of what? sagt:Unterschied zwischen Service-Schicht und Controller in der Praxis

Ich versuche Controller zu beschränken, um Arbeit zu tun in Bezug auf die Validierung http Parameter, zu entscheiden, welche Service-Methode mit zu nennen, welche Parameter, , was in der Http oder Anfrage zu stellen, welche im Hinblick auf Redirect oder weiterleiten, oder ähnliche Web-bezogene Sachen.

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

Red Flags: Mein Controller-Architektur schlecht gehen könnte, wenn:

The Controller zu viele Anfragen an die Schicht Service macht. Der Controller führt eine Anzahl von Anforderungen an die Service-Schicht aus, die keine Daten zurückgeben. Der Controller stellt Anfragen an die Service-Schicht , ohne Argumente zu übergeben.

Im Moment habe ich eine Web-App mit Spring MVC entwickle, und ich habe ein solches Verfahren zum Speichern E-Mail-Benutzers geändert:

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

Sie können sehen, dass ich viele Anfragen an den Dienst haben Schicht, und ich umleiten von Controller - das ist Geschäftslogik. Bitte zeigen Sie eine bessere Version dieser Methode.

Und ein weiteres Beispiel. Ich habe diese Methode, die Benutzer-Profile zurück:

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

Methode convertAvaForRendering (clientFromDB.getAvatar()) in Superklasse dieser Controller gesetzt wird, ist es richtig Platzierung dieser Methode, oder er muss in Betrieb genommen werden Schicht??

Hilfe bitte, es ist wirklich wichtig für mich.

+0

Sie sollten die "Der Controller macht zu viele Anfragen an die Service-Schicht vergessen. Der Controller macht eine Anzahl von Anfragen an die Service-Schicht, die keine Daten zurückgeben. Der Controller macht Anfragen an die Service-Schicht, ohne Argumente übergeben. " klingt wie * non-sense * zu mir –

+0

"Bitte bessere Version dieser Methode zeigen?", versuchen codereview.stackexchange.com –

Antwort

3

In beiden Beispielen, warum müssen Sie Client werfen? Das ist ein Code-Geruch.

Da der Aufruf an die Serviceebene auch der Aufruf ist, der die Datenbanktransaktionsgrenze festlegt, führt das Ausführen mehrerer Aufrufe dazu, dass sie in verschiedenen Transaktionen ausgeführt werden und daher nicht konsistent sind.

Das ist einer der Gründe, warum mehrere Anrufe abzuraten sind. @ArthurNoseda erwähnt andere gute Gründe in his answer.

In Ihrem ersten Fall sollte ein einzelner Anruf an die Service-Tier, z. etwas wie das:

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

Sie könnten auch Rückgabewert anstelle von Ausnahmen verwenden.

Wie Sie sehen, wird dies die Geschäftslogik delegieren, die E-Mail an die Service-Ebene zu ändern, während alle UI-bezogenen Aktionen im Controller bleiben, wo sie hingehören.

+0

Vielen Dank zum Beispiel) Ich habe zu Client umgewandelt, weil ich in meiner Web-App Klasse Benutzer habe, und zwei Unterklassen, Client und Translator, und ich habe abstrakte Klasse UserService - die einige gemeinsame Methoden für Benutzer und Übersetzer haben, und weil diese Methode in ClientController (aber nicht in TranslatorController) platziert wird, kann ich in Client umwandeln. Wenn es schlecht ist, sag mir bitte besseren Weg dafür :) – Yuriy

+0

@Yuriy Wie kann der * service * wissen, ob man für den gegebenen Benutzer einen 'Client' oder einen' Translator' zurückgibt? Sie sollten zwei Servicemethoden haben, eine für jeden Typ. – Andreas

+0

Also, ich werde Common Interface UserService und zwei Unterklassen ClientService und TranslatorService erstellen. Danke :) – Yuriy

4

ein Frühlings-Controller ist in der Regel (wie Model mit Klassen, ModelAndView ...) für die Frühjahrstagung des API gebunden oder der Servlet API (HttpServletRequest, HttpServletResponse ...). Methoden können String Ergebnisse zurückgeben, die in den Namen einer Vorlage aufgelöst werden (JSP ...). Controller sind sicherlich Web-GUIs voreingenommen, mit einer starken Abhängigkeit von Web-Technologie.

Service s auf der anderen Seite sollte mit Business-Logik im Auge behalten und keine Annahmen über den Client. Wir könnten den Dienst remote schalten, ihn als Web-Service verfügbar machen, ein Web-Frontend oder einen Swing-Client implementieren. A Servicesollte nicht von Spring MVC, Servlet API und dergleichen abhängen. Auf diese Weise können Sie den Großteil der Geschäftslogik wiederverwenden, wenn Sie Ihre Anwendung neu ausrichten müssen.

Wie für den Hinweis zu viele Aufrufe an die Service-Schicht von der Controller-Schicht, ist es meistens eine Frage der Leistung, die IMHO ist etwas anderes. Wenn bei jedem Aufruf der Serviceebene eine Datenbank abgefragt wird, können Leistungsprobleme auftreten. Wenn der Service-Layer und der Controller-Layer nicht in derselben JVM ausgeführt werden, können auch Leistungsprobleme auftreten.Dies ist ein weiterer sehr wichtiger Aspekt beim Entwerfen Ihrer Anwendung, aber es würde bedeuten, dass Sie Ihre Dienste aufrufen sollten, um gröbere Operationen für die Controller-Schicht bereitzustellen.