22.12.08 22:29 작성
·
465
·
수정됨
1
안녕하세요. 로드맵 수강하고 토이 프로젝트 진행 중인데 궁금한 점이 계속 나오네요..
일반적으로 사용하는 표현 - 비즈니스 - 도메인 계층 구조에서 비즈니스 정책을 검증하는 위치와 관련해서 질문 드립니다.
예를 들어 사용자용 게시글 삭제 API는 현재 요청 사용자가 게시글의 작성자가 맞는지 검사합니다. 이 검증 작업을 서비스 계층에서 처리했습니다.
public class PostService {
private final PostRepository postRepository;
// 게시글 삭제 기능
public void deletePost(Long postId, Long currentMemberId) {
// 엔티티 조회 후 게시글 작성자가 현재 요청 사용자( currentMemberId )와 일치하는지 검사
Post post = postRepository.findById(postId);
if (!post.getWriter().getId().equals(currentMemberId)) {
// 작성자가 아니면 예외 발생
}
post.delete();
}
}
이 메서드를 사용자 API 컨트롤러에서는 잘 사용했는데, 관리자 API는 게시글 작성자 검증이 필요 없다는게 문제였습니다. 사용자 및 관리자용 API 컨트롤러에서 호출하는 것은 결국 '게시글 삭제'라는 동일한 기능이므로 하나의 서비스 클래스에서 제공하는 게 맞다 판단하여 deletePost()
메서드에서 수행하던 유효성 검증 로직을 사용자 API 컨트롤러로 이관했습니다.
public class PostController {
private final PostService postService;
// 실제 코드는 아니며 설명용 코드입니다.
// 예를 들어 currentMemberId의 경우 실제론 @AuthenticationPrincipal 등을 통해 얻습니다.
@DeleteMapping("/{postId}")
public void delete(Long currentMemberId, @PathVariable Long postId) {
Post post = postService.findPost(postId);
if (!post.getWriter().getId().equals(currentMemberId)) {
// 작성자가 아니면 예외 발생
}
postService.deletePost(postId);
}
}
이렇게 구성하니 PostService
를 사용자 API 컨트롤러에서도, 관리자 API 컨트롤러에서도 사용 가능했지만.. 표현 계층에서 비즈니스 정책을 검증하니 구조가 이상한 것 같아서 질문 드립니다.
'게시글을 삭제하려는 사용자가 게시글의 작성자인가?'를 확인하는 작업은 비즈니스 정책에 의한 작업이라 생각합니다. 그러므로 서비스 계층에서 검사하는 게 맞다 생각되는데, 관리자 API에서 사용하기 불편하더라구요. 이런 경우 구조를 어떻게 가져가시나요?
PostService
는 비즈니스 로직만 처리하고, PostUserService
같은 래퍼 서비스를 만들어 검증 부분을 처리할까 했는데, 썩.. 맘에 들진 않았습니다.
예시처럼 정책 검증을 컨트롤러에서 수행한다 가정하면, 제 코드의 경우 PostService.findPost()
메서드에 @Transactional(readOnly=true)
를 적용한 상태라 deletePost()
메서드에서 SELECT 쿼리를 또 실행합니다.
@Transactional(readOnly=true)
은 정말 필요한 곳에만 선택적으로 적용하시나요? 아니면 읽기 전용 메서드는 별도로 구분하시나요?
'서비스는 최대한 비즈니스 로직만 수행하고, 정책 유효성 검증은 다른 위치에서 진행하는 것이 좋다'라는 분들도 있던데, 보통 어떤 식으로 처리하시나요?
답변 1
2
안녕하세요. InJun Choi님, 공식 서포터즈 David입니다.
"게시글을 삭제하려는 사용자가 게시글의 작성자인가?"가 정책이라면 해당 정책이 관리자를 뺀 사용자에 대한 검증만 포함하고 있고 그 정책을 그대로 구현하다보니 관리자에 대한 부분을 처리하기 어려운 것 같습니다. 애초에 정책 단계에 관리자에 대한 것도 포함 시켜야 할 것 같습니다. 그러면 자연스럽게 관리자 식별에 대한 부분도 고민하게 되고 하다못해 관리자를 식별할 수 있는 플래그를 넘겨 받는 파라미터라도 메서드 시그니처에 추가하게 될 수 있을 것 같습니다. 사용자, 관리자 구분의 경우 3번 내용을 참고해주세요.
선택적으로 사용하다가 읽기 전용 메서드가 늘어나면 CQS 패턴을 적용해서 읽기 전용 메서드들을 모아둔 xxxQueryService를 별도로 구성할 수 있습니다.
여러가지 방법이 있겠지만 단순 사용자 검증의 경우 필터나 인터셉터 또는 HandlerMethodArgumentResolver를 사용해서 처리하는 경우가 많고 검증 책임을 별도의 서비스로 분리하여 처리할 수도 있습니다.
감사합니다.
2022. 12. 09. 16:11
David님 답변 감사합니다.
현재 스프링 시큐리티를 이용해 관리자 권한을 가진 경우에만 관리자 API(
/api/admin/**
) 호출을 허용 중입니다. 즉, '관리자 핸들러 메서드 호출 성공 = 관리자 권한이 있다'라고 이미 확인된 상태이다 보니 서비스 클래스에서 다시 검증하는 게 꺼려졌던 것 같습니다.아무래도 권한에 따른 정책이니 표현 계층에서 검증하지 말고, 권한 정보를 비즈니스 계층으로 넘기는 게 맞을까요?
제 예시 말고도 권한에 따라 동작이 바뀌는 경우가 꽤 있을 것 같습니다. 매번 서비스에서 관련 정보를 메서드 파라미터로 받아야 할까요? 아니면 다른 방법이 있을까요? 일반적으로 권한에 따른 처리를 어떤 식으로 구성하는지 알고 싶습니다.