make it simple
article thumbnail

응용영역 & 표현 영역의 역할이란? 

  • 도메인이 제 기능을 하려면 사용자와 도메인을 연결해주는 매개체가 필요하다. 그 매개체가 바로 표현 영역 & 응용 영역이다. 예를 들어, 사용자가 웹 브라우저에서 form에  ID 와 Password를 입력한 후 Submit을 하면 표현 영역에서 URL, 쿠키, 헤더, 요청 parameter 등을 이용해 사용자가 실행하고 싶은 기능을 판별하고  응용 영역에 넘긴다. 그 기능은 실질적으로 응용 서비스에서 로직이 실행된다.


응용 서비스의 역할

주로 도메인 객체간의 흐름을 제어하기 때문에 최대한 밑에 코드처럼 단순한 형태를 갖어야한다.

  • 복잡한 형태 -> 응용 서비스가 도메인 로직을 갖고 있는 경우(도메인 로직은 무조건 도메인에 그래야 코드 중복, 로직 분산 등을 피해 코드 품질을 높일 수 있다.)
public Result doSomeFunc(SomeReq req){
    // 1. 리포지터리에서 애그리거트를 구한다.
    SomeAgg agg = someAggRepository.findById(req.getId());
    
    // 2. 애그리거트의 도메인 기능을 실행한다.
    agg.doFunc(req.getValue());
    
    // 3. 결과를 리턴한다. 
    return createSuccessResult(agg);
}

 

도메인 로직을 서비스 영역에 안 넣도록 주의 해야한다.

  • 도메인 로직이 서비스에 포함되어 있는 나쁜 예
public class ChangePasswordService{

	public void changePassword(String memberId, String oldPw, String newPw){
    	Member member = memberRepository.findById(memberId);
        checkMemberExists(member);
        
        // 도메인에 속해야하는 기존 비밀번호 검증 로직
        if(!passwordEncoder.matches(oldPw, member.getPassword()){
        	throw new BadPasswordException
        }
        
        member.setPassword(newPw);
    }
}
  • 좋은 예
public class chagnePasswordService{
	public void changePassword(String memberId, String oldPw, String new Pw){
    	// 해당 id의 멤버 데이터 찾기
    	Member member = memberRepository.findById(memberId);
        // 멤버 존재하는지 찾는 함수
        checkMemberExists(member);
        // 비밀번호 변경 
        member.changePassword(oldPw,newPw);
    }
}


public class Member{
	public void changePassword(String oldPw, String newPw){
    	if (!matchPassword(oldPw)) throw new BadPasswordException();
        setPassword(newPw);
    }
    
    // 변경전 기존비밀번호가 맞는치 체크하는 메서드
    public boolean matchPassword(String pwd) {
    	return passwordEncoder.matches(pwd);
    }
    
    // 비밀번호 변경하는 메서드 내부에서만 사용되게 접근제한자 private
    private void setPassword(String newPw){
    	if (isEmpty(newPw)) throw new IllegalArgumentException("no new password");
    }
}
  • 위에 좋은 예처럼 기존 비밀번호 변경하는 로직이 도메인에 속하는 경우 응용 서비스에서 해당 로직을 또 구현하지 않고 도메인에 속해있는 로직을 사용함으로써 코드 중복을 줄이고 응집도를 높힌다.

응용 서비스의 크기

회원 도메인을 생각하면 응용 서비스에서 구현해야하는 기능은  회원 가입, 회원 탁퇴, 회원 암호 변경, 비밀번호 초기화 등이 있다 가정하자.  응용 서비스 구현 방법은 두가지가 있다. 

  • 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현
    • 장점: 각 기능의 동일한 로직을 위한 코드 중복 제거가 쉽다. 
    • 단점: 서비스 클래스의 크기(코드 수) 가 커진다. 크기가 커지면 의존하는 객체들도 늘어나고 코드가 점점 얽히게 되어 코드 품질을 낮춘다.
  • 구분되는 기능별로 응용 서비스 클래스 따로 구현
    • 장점: 각 클래스별로 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코드에 영향을 받지 않는다. 클래스를 기능별로 나눔으로 유지보수/가독성이 좋아지며 코드 품질을 일정 수준으로 유지하는데 도움이 된다.  
    • 단점: 클래스가 많아진다.

습관적으로 모든 기능들을 한 도메인 응용서비스에 억지로 몰아넣고 개발할 생각보단 만약 해당 도메인에 대한 기능이 많아지면 기능별로 분리하는 방법을 생각하는 습관을 가지는게 좋다.

 

인터페이스를 무조건 사용하기보단 명확히 이유가 있을 때 사용해야한다.  무의식적인 인터페이스 생성은 소스파일만 많아지고 구현 클래스에 간접 참조가 증가해서 전체구조가 복잡해진다.-> ex) 구현 클래스가 여러개일 때

 


표현 영역에 의존하지 않기

  • 절대로 도메인 자체를 표현 영역(controller)에 return하면 안된다. 응용 서비스는 표현 영역에서 필요한 데이터만 리턴을 해줘야한다 -> 응용 서비스(service)와 표현 영역(controller)의 책임을 정확히 나누며 각 영역의 응집도를 높힌다.
  • 응용 서비스의 parameter 타입을 사용할때 표현 영역과 관련된 타입을 전달하면 안된다. 밑에 처럼 표현 영역에 해당하는 HttpServletRequest/HttpSession 등을 서비스에 파라미터로 넘기면 안 된다.
    • 응용 서비스에서 표현 영역에 의존이 발생함으로 응용 서비스만 단독으로 테스트하기가 어려워진다.
    • 응용 서비스가 표현 영역의 역할까지 대신할 상황이 벌어질 수도 있다. HttpSession/cookie 는 표현 영역의 상태에 해당하는데 만약 서비스에서 변경해버리면 상태가 어떻게 변경되는지 추적하기 힘들어진다.
@Controller 
@RequestMapping("/member/changePassword") 
public class MemberPasswordController {
    @PostMapping
    public String submit(HttpServletRequest request) {
        try {
            // 응용 서비스가 표현 영역을 의존하면 안 됨!
            changePasswordService.changePassword(request); 
        } catch(NoMemberException ex) {
            // 알맞은 익셉션 처리 및 응답
        }
    }
}

 

2편에서 계속 ...


reference

profile

make it simple

@keep it simple

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!