간단한 웹 어플리케이션을 개발할 때 사용하는 전형적인 구조는 다음 요소를 포함한다.
서비스는 핵심이 되는 기능의 로직을 제공한다고 했다. 예를 들어 비밀번호 변경 기능은 다음 로직을 서비스에서 수행한다.
위와 같은 과정을 중간 과정에서 실패가 나면 이전 까지 했던 것을 취소해야 하고, 모든 과정을 성공적으로
진행했을 때 완료해야 한다. 이런 이유로 서비스 메서드를 트랜잭션 범위에서 실행해야 한다.
각 서비스 클래스는 기능 제공을 위해 한 개의 public 메서드를 제공하고 있다.
같은 데이터를 사용하는 기능들을 한 개의 서비스 클래스에 모아서 구현할 수도 있다.
필요한 데이터를 전달받기 위해 별도 타입을 만들면 스프링 MVC의 커맨드 객체로 해당 타입을 사용할 수 있어 편하다.
회원 가입 요청을 처리하는 컨트롤러 클래스의 코드는 다음과 같이 서비스 메서드의 입력 파라미터로 사용되는 타입을 커맨드 객체로 사용했다.
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq, Errors errors) {
...
memberRegisterService.regist(regReq);
}
비밀번호 변경의 changePassword() 메서드처럼 웹 요청 파라미터를 커맨드 객체로 받고 커맨드 객체의 프로퍼티를 서비스 메서드에 인자로 전달할 수도 있다.
@RequestMapping(method = RequestMethod.POST)
public String submit(@ModelAttribute("command") ChangePwdCommand pwdCmd, Errors errors, HttpSession session) {
...
changePasswordService.changePassword(
authInfo.getEmail(),
pwdCmd.getCurrentPassword(),
pwdCmd.getNewPassword());
...
}
커맨드 클래스를 작성한 이유는 스프링 MVC가 제공하는 폼 값 바인딩과 검증, 스프링 폼 태그와의 연동 기능을 사용하기 위함이다.
기능을 실행한 후에 결과를 알려주어야 한다. 결과는 크게 두가지 방식으로 알려준다.
위 두가지 예시를 보여주는 예제 코드 AuthService 클래스이다.
public class AuthService {
...
public AuthInfo authenticate(String email, String password) {
Member member = memberDao.selectByEmail(email);
if(member == null) {
throw new WrongIdPasswordException();
}
if(!member.matchPassword(password)) {
throw new WrongIdPasswordException();
}
return new AuthInfo(member.getId(), member.getEmail(), member.getName());
}
}
서비스의 로직이없이 1개의 DAO만 접근하는 기능을 수행한다면
반드시 컨트롤러는 서비스를 이용해야 한다는 압박에서 벗어나 다음과 같이 DAO에 직접 접근해도 큰 틀에서는 웹 어플리케이션의 계층 구조는 유지된다고 본다.
@RequestMapping("/member/detail/{id}")
public String detail(@PathVariable("id") Long id, Model model) {
Member member = memberDao.selectByEmail(id);
...
}