작심십사일러의 스프링 시작하기(14)

서은경·2022년 9월 14일
0

Spring

목록 보기
24/43

길고 긴 실습은 거의 끝이 났다
이번엔 간단한 웹 어플리케이션의 구조에 대해 정리해보려고 한다!

간단한 웹 어플리케이션의 구성 요소

간단한 웹 어플리케이션을 개발할 때 사용하는 전형적인 구조는 다음 요소를 포함한다.

  • 프론트 서블릿
    💡 웹 브라우저의 모든 요청을 받는 창구 역할
    프론트 서블릿은 요청을 분석해서 알맞은 컨트롤러에 전달하며 스프링 MVC에서는 DispatcherServlet이 프론트 서블릿의 역할을 수행한다.
  • 컨트롤러 + 뷰
    💡 컨트롤러는 실제 웹 브라우저의 요청을 처리한다.
    컨트롤러는 클라이언트(브라우저)의 요청을 처리하기 위해 알맞은 기능을 실행하고 그 결과를 뷰에 전달한다. 컨트롤러의 주요 역할은 다음과 같다.
    - 클라이언트가 요구한 기능을 실행
    - 응답 결과를 생성하는데 필요한 모델 생성
    - 응답 결과를 생성할 뷰 선택
    컨트롤러는 어플리케이션이 제공하는 기능과 사용자 요청을 연결하는 매개체로서 기능 제공을 위한 로직을 직접 수행하지는 않는다. 대신 해당 로직을 제공하는 서비스에 그 처리를 위임한다.
  • 서비스
    💡 서비스는 핵심이 되는 기능의 로직을 구현한다
  • DAO
    💡 서비스에서 DB연동이 필요할 때 DAO를 사용한다.
    DB와 웹 어플리케이션 간에 데이터를 이동시켜 주는 역할을 맡는다. 어플리케이션은 DAO를 통해 DB에 데이터를 추가하거나 DB에서 데이터를 읽어온다. 목록이나 상세 화면과 같이 데이터를 조회하는 기능만 있고 부가적인 로직이 없는 경우에는 컨트롤러에서 직접 DAO를 사용하기도 한다. (자세한건 DAO vs Repository 글 참고)

서비스의 구현

서비스의 구현에 대해 비밀번호 변경 예시를 들어 내용을 자세하게 풀어보면
비밀번호 변경 기능을 위한 로직에는

  • DB에서 비밀번호를 변경할 회원의 데이터를 구한다
  • 존재하지 않으면 익셉션을 발생시킨다
  • 회원 데이터의 비밀번호를 변경한다
  • 변경 내역을 DB에 반영한다

이렇게 있다.

웹 어플리케이션을 사용하든 명령행에서 실행하든 비밀번호 변경 기능을 제공하는 서비스는 동일한 로직을 수행한다. 이런 로직들은 한 번의 과정이 아닌 몇 단계의 과정을 거치며 중간 과정에서 실패가 나면 이전까지 했던 것을 취소하고 모든 과정을 성공적으로 진행했을 때 완료해야 한다.
이런 이유로 서비스 메서드를 트랜잭션 범위에서 실행한다.

package spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

public class ChangePasswordService {

    @Autowired
    private MemberDao memberDao;

    @Transactional
    //(value = "test") 이 속성 없으면 PlatformTransactionManager 타입으로 찾아서 사용
    public void changePassword(String email, String oldPwd, String newPwd) {
        Member member = memberDao.selectByEmail(email);
        if(member == null)
            throw new MemberNotFoundException();

        member.changePassword(oldPwd, newPwd);

        memberDao.update(member);
    }

    public void setMemberDao(MemberDao memberDao) {
        this.memberDao = memberDao;
    }
}

서비스를 구현할 때 한 서비스 클래스에 제공할 기능의 개수는 몇개가 적당할까 ?! 답은 따로 없지만 상황이나 실무에 맞게 같은 데이터를 사용하는 기능들을 한 개의 서비스 클래스에 모아 구현하거나 기능별로 서비스 클래스를 작성하면 된다.

🙋‍♀️ 기능별 서비스 클래스를 작성하면 장점이 뭔가요?
💡 한 클래스의 코드 길이를 일정 수준 안에서 유지할 수 있어서 기존 코드를 수정하거나 기능 확장이 용이하다.

서비스 메서드는 기능 실행 후 크게 두 가지 방식으로 결과를 알려준다.

  • 리턴 값을 이용한 정상 결과
  • 익셉션을 이용한 비정상 결과
package spring;

public class AuthService {

    private MemberDao memberDao;

    public void setMemberDao(MemberDao memberDao) {
        this.memberDao = memberDao;
    }

    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());
    }
}

이 서비스 클래스를 호출한 클래스에서 결과에 따른 분기처리가 이루어진다.

컨트롤러에서의 DAO 접근

서비스 메서드에서 어떤 로직도 수행하지 않고 단순히 DAO의 메서드만 호출하고 끝나는 코드도 있다.
예를 들어 회원 데이터 조회를 위한 서비스 메서드를 다음과 같이 구현하곤 한다.

public class MemberService {

    public Member getMember(long id) {
    	return memberDao.selectById(id);
    }
}

이 서비스 메서드를 이용해 컨트롤러에서 회원 정보를 구한다.

	@GetMapping("/members/{id}")
    public String detail(@PathVariable("id") Long memId, Model model) {
        Member member = memberService.getMemberId(memId);
        if (member == null) {
            throw new MemberNotFoundException();
        }
        model.addAttribute("member", member);
        return "member/memberDetail";
    }

이렇게 되면 사실상 memberDao.selectById() 메서드를 실행하는 것과 동일하다. 이 경우 컨트롤러는 서비스를 사용해야 한다는 압박에서 벗어나 직접 DAO에 접근해도 큰 틀에서 웹 어플리케이션의 계층 구조는 유지된다고 본다.

	@GetMapping("/members/{id}")
    public String detail(@PathVariable("id") Long memId, Model model) {
        Member member = memberDao.selectById(memId);
        if (member == null) {
            throw new MemberNotFoundException();
        }
        model.addAttribute("member", member);
        return "member/memberDetail";
    }

‼️ 컨트롤러에서 서비스 계층을 거치지 않고 바로 데이터 접근 계층의 DAO를 사용하는 방식은 개발자마다 호불호가 갈린다. 어떤 방식이 좋다는 식의 정답은 없으니 서비스의 역할과 DAO의 역할을 정의해나가며 선호하는 방식을 정립해나가면 된다 ‼️

패키지 구성

웹 요청을 처리하기 위한 영역에는 컨트롤러 클래스와 관련 클래스들이 위치한다. 커맨드 객체의 값을 검증하기 위한 Validator도 웹 요청 처리 영역에 위치할 수 있는데 관점에 따라 Validator를 기능 제공 영역에 위치시킬 수도 있다.

기능 제공 영역에는 기능 제공을 위해 필요한 서비스, DAO, 그리고 Member와 같은 모델 클래스가 위치한다. 기능 제공 영역은 다시 service, dao, model과 같은 세부 패키지로 구분하기도 한다.

0개의 댓글