SideProject/개발일지

3-layer-architecture에서의 Command 객체에 대해서

박세류 2024. 12. 9. 23:25

레이어드 아키텍처는 가장 기초적인 제약을 지켜야 한다고 생각한다.

가장 기초적인 제약이란 레이어 간 의존 방향은 단방향을 유지해야 한다는 것이다.

    public User create(UserCreateRequest userCreateRequest) {
        return userRepository.save(User.from(userCreateRequest));
    }

위 코드 중 UserCreateRequest는 API(presentation)레이어의 모델이다.

API로 들어오는 요청을 Body인 @RequestBody로 매핑하려고 만든 객체인데,

하위 레이어인 서비스 컴포넌트로 전달해 서비스에서 이를 사용하고 있다.

이는 양방향 의존성이 생겼다는 말이고, 양방향 의존은 순환 참조가 생겼다는 말과 같다.

레이어 간에 양방향 의존성이 생겼다는 사실은 분리된 레이어가 하나로 통합되었다는 말과 같으므로 계층 관계를 사용하는 의미가 퇴색되며, 결론적으론 안티패턴이라고 생각한다.

그렇다면 해결책은?

-> 레이어별 모델 구성

비즈니스 레이어에 위치하는 모델을 추가로 만든다. 아래와 같은 명명 규칙을 사용할 수 있다.

  1. ~Request는 API요청을 처리하는 dto
  2. ~Command 클래스는 서비스에 생성, 수정, 삭제 요청을 보낼 때 사용하는 dto이다.

이처럼 클래스를 구분하고 컨트롤러가 서비스에 요청을 보낼때 RequestCommand 클래스로 변경해 서비스의 메서드를 호출한다.

@Getter
@Builder
@RequiredArgsConstructor
public class CoupleCreateRequest implements BaseRequest<Couple> {

    private final String senderEmail;
    private final String receiverEmail;

    @Override
    public CoupleCreateCommand toCommand() {
        return new CoupleCreateCommand(senderEmail, receiverEmail);
    }
}
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void createCouple(@RequestBody CoupleCreateRequest coupleRequest) {
        coupleService.createCouple(coupleCreateRequest.toCommand());
    }

 

위 구조를 차용했을 때 장점은 다음과 같다.

  1. 의존 방향이 단방향이 되고 순환 참조가 사라진다.
  2. 클라이언트가 API 요청을 보내는 시점의 request body와 서비스 컴포넌트가 사용하는 DTO를 분리할 수 있게 된다.따라서, request 객체에는 userId 필드를 넣지 않고, command 객체에는 이 값을 갖고 있게 한뒤, userId 값은 UserPrincipal (AuthUser) 같은 신뢰할 수 있는 값에서 가져오면 된다.                                                                          이는 Command 클래스에 필요한 정보를 꼭 Request 클래스에서 가져올 필요가 없다는 것이고, Request 클래스는 @RequestBody, @ModelAttribute 애너테이션을 처리하며, Command 클래스는 도메인 객체를 만들기 위한 정보를 가진다.라는 역할을 분리할 수 있게 된다.

하지만 단점또한 있는데,

  1. 코드의 양이 늘어난다.
    • 예시로 든 Couple 도메인에 Command 객체를 도입하게 되면서 생성된 연관 클래스는 다음과 같다.
      • Couple
      • CoupleCreateRequest
      • CoupleCreateCommand
    즉 CRUD를 위한 4개의 DTO * 레이어 개수 만큼 클래스가 더 생성된다.

하지만, 필드가 겹친다는 이유로 클래스를 공유하는 것 보다는 역할과 책임에 따라 확실하게 구분하는 편을 선호하여 위 구조를 사용하는 편이다. (취향은 다양한 것 같다)

728x90