책/도메인 주도 개발

[DDD] 도메인 주도 개발 시작하기 CH_6. 응용 서비스와 표현영역 2편

keep it simple 2023. 3. 6. 02:21

표현 영역의 3가지 책임

  • 사용자가 시스템을 사용할 수 있는 흐름을 제공하고 제어한다.
  • 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
  • 사용자의 세션을 관리한다.

값 검증

  • 값 검증은 표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다.

응용 서비스에서의 검증

public class JoinService {
	
    @Transactional
    public void join(JoinRequest joinReq){
    	// 값의 형식 검사
        checkEmpty(joinReq.getId(), "id");
        checkEmpty(joinReq.getName(), "name");
        
        if (joinReq.getPassword().equarls(joinReq.getConfirmPassword()))
        	throw new InvalidPropertyException("confirmPassword");
    }
    
    private void checkEmpty(String value, String propertyName){
    	if (value == null || value.isEmpty())
        	throw new EmptyPropertyException(propertyName);
    }
}

표현 영역에서의 검증

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;

public class JoinRequest {

    @NotBlank  // string length가 0보다 커야되는 validator
    private final String userId;
    @NotBlank
    private final String password;
    @NotBlank
    private final String name;
    
}
@PostMapping("/user/create")
public ResponseEntity<String> joinUser(@Valid JoinRequest joinReq) {
     return ResponseEntity.ok("valid");;
}

위처럼 표현 영역/응용 서비스 영역에서 둘 다 검증이 가능하다. 좋은 방법은 표현 형역에서는 필수 값 형식을 검사하고 응용 서비스는 ID의 중복 여부와 같은 논리적 오류를 검사하면 코드가 간결해진다.

  • 표현 영역: 필수 값, 값의 형식, 범위 등을 검증
  • 응용 서비스: 데이터의 존재 유무와 같은 논리적 오류를 검증

권한 검사

  • 권한 검사를 수행할 수 있는 계층은 표현 영역/응용 서비스/도메인 세가지가 있다.

표현 영역에서 검사

  • 많은 프로젝트들이 권한별로 기능이 분리되어있는 경우가 많다. 보통 스프링 시큐리티를 많이 사용하지만, 해당 프레임워크에 대한 이해도가 부족하면 무턱대고 프레임워크를 사용하기 보단 간단하게 구현하는게 더 나을 수 있다.
  • 표현 영역에서 할 수 있는 기본적인 검사는 인증된 사용자인지 아닌지 검사하는 것이다.
    • 예시) 회원 정보 변경 기능은 인증된 사용자만 회원 정보 변경 기능 URL에 접근할 수 있다. ->  서블릿 필터 사용
     

  • 위 처럼 사용자의 인증을 생성하고 인증 여부를 검사한다. 인증된 사용자면 다음 과정을 진행하고 그렇지 않으면 로그인 화면이나 에러 화면을 보여주면 된다.

응용 서비스에서 검사

  • URL 만으로 접근 제어를 할 수 없는 경우 응용 서비스에서 메서드 단위로 권한 검사를 수행해야한다. 스프링 시큐리티 AOP를 활용해서 @PreAuthorize("hasRole('ADMIN')") 같이 hasRole속성을 사용해 role에 따라 권한을 검사할 수 있다. 밑에 처럼 사용자를 정지 시키려면 관리자의 권한을 가진 사용자만이 해당 메서드의 기능을 사용할 수 있다.
public class BlockMemberService {
    private MemberRepository memberRepository;
    
    @PreAUthorize("hasRole('ADMIN')")
    public void block(String memberId){
    	Member member = memberRepository.findById(memberId);
        if (member == null) throw new NoMemberException();
        member.block();
    }
}

 

도메인 객체 검사

  • 예를 들어 게시글 삭제는 본인 또는 관리자 역할을 가진 사용자만 삭제할 수 있다. 이런 경우 서비스 단위로 구현이 어렵다. 그러므로 게시글 애그리거트를 먼저 로딩하고 직접 권한 검사 로직을 구현해야한다. 밑처럼 checkDeletePermission()함수를 사용해 검사 로직을 직접 구현했다.
public class DeleteArticleService{
	
    public void delete(String userId, Long articleId){
    	Article article = articleRepository.findById(articleId);
        checkArticleExistence(article);
        permissionService.checkDeletePermission(userId,article);
        article.markDeleted();
    }
}

조회 전용 기능과 응용 서비스

  • 무조건 서비스를 생성해야한다는 고정관념을 버려야한다. 만약 서비스에서 추가적인 로직이 없고 단일 쿼리만 실행하는 조회 전용 기능이면 트랜잭션도 필요없다. 굳이 서비스를 만들 필요 없이 표현 영역에서 바로 조회 전용 기능을 사용해도 문제가 없다.
public class OrderController{
    private OrderViewDao orderViewDato;
    
    @RequestMapping("/myorders")
    public String list(ModelMap model){
    	List<OrderView> orders = orderViewDao.selectByOrderer(orderId);
        model.addAttribute("orders",orders);
    	reutrn "order/list";
    }
}


결론

  • 항상 개발을 할 때 계층에 대한 역할과 책임을 잘 몰랐다. 해당 챕터를 공부하면서 완벽하게 응용서비스 영역/표현 영역의 책임과 역할을 깨달았다고 할 순 없지만, 확실히 어떻게 책임과 역할을 나눠야 되는지 배운 좋은 챕터인거 같다. 또한, 항상 개발할 때 습관적으로 만들기 보단 만드는 클래스가 꼭 필요한가를 생각해보고 사용해야겠다.

reference