본문 바로가기

DDD/도메인 주도 설계 철저 입문

파사드 패턴으로 유스케이스 구현하기 - 「도메인 주도 설계 철저 입문」 6장 (2)

 

 

 

본 포스트 시리즈는 「도메인 주도 설계 철저 입문」책을 요약한 내용입니다.

 

 

이전 발행 글 보기

 

 

더보기
닫기

2024.09.04 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 2장 (1)

2024.09.04 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 2장 (1)

2024.09.05 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 2장 (2)

2024.09.09 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 3장 (1)

2024.09.09 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 3장 (1)

2024.09.11 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 3장 (2)

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 4장

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 5장 (1)

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 5장 (2)

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 5장 (3)

2024.10.05 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 6장 (1)


 


 

 


 

06장 유스케이스를 구현하기 위한 '애플리케이션 서비스'

 

(1) 애플리케이션 서비스 구현과 DTO 활용

 

 

애플리케이션 서비스 (2) Read

CRUD 중 Read를 구현한 코드는 다음과 같다. 

public class UserApplicationService {
    private final IUserRepository userRepository;
    private final UserService userService;

    public UserApplicationService(IUserRepository userRepository, UserService userService) {
        this.userRepository = userRepository;
        this.userService = userService;
    }
    
    public UserData get(String userId) {
        UserId targetId = new UserId(userId);
        User user = userRepository.find(targetId);

        if (user == null) {
            throw new IllegalArgumentException("사용자를 찾을 수 없습니다.");
        }

        return new UserData(user.getId().getValue(), user.getName().getValue(), user.getMailAddress().getValue());
    }
}

 

 

DTO의 사용

주목할 점은 사용자 데이터를 조회하는 "get" 함수의 반환 값을 User 클래스가 아닌 새로 정의한 DTO인 `UserData`로 구현한 점이다. User 인스턴스가 `UserApplicationService`에 의해 외부에 공개되어버리면, `UserApplicationService`의 클라이언트들이 이를 조작할 수 있는 문제가 있다.

 

접근제어자를 활용해서 User의 setter를 감출 수 있다고는 하나 같은 패키지에 있는 객체로부터 보호하기는 어렵다. 따라서 User엔티티를 직접 반환하는 것이 아니라 DTO를 반환하는 것이 좋다. 

 

 

DTO의 생성자

이때 DTO의 생성자가 User의 아이디, 이름 등 필드를 개별 인자로 받게 되면 파라미터가 추가될 때마다 UserData를 생성하는 모든 곳에서 수정을 해주어야 하기 때문에  DTO의 생성자는 엔티티를 직접 받는 것이 좋다. 이렇게 하면 파라미터가 추가될 때 UserData 클래스만 수정하면 된다.

public class UserData {
    private final String id;
    private final String name;

    public UserData(User source) {
        this.id = source.getId().getValue();   // User 도메인 객체에서 id를 가져옴
        this.name = source.getName().getValue(); // User 도메인 객체에서 name을 가져옴
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

 

 

파사드 패턴

사용자 정보를 수정할 때는, 수정할 필드가 때마다 다를 수 있기 때문에 다음과 같은 커맨드 객체를 정의하여 활용하는 것이 좋다. 커맨드 객체란 클라이언트가 전달해주는 파라미터 데이터를 주입 받기 위해 사용되는 객체를 말한다.

public class UserUpdateCommand {
    private final String id;
    private final String name;
    private final String mailAddress;

    public UserUpdateCommand(String id, String name, String mailAddress) {
        this.id = id;
        this.name = name;
        this.mailAddress = mailAddress;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getMailAddress() {
        return mailAddress;
    }
}

 

 

 

커맨드 객체(수정을 위한 DTO라고 볼 수 있다) 를 사용하면 update 메소드 시그니처가 다음과 같이 한 가지, `public void update(UserUpdateCommand command)`만 있으면 된다.

public class UserApplicationService {
    private final IUserRepository userRepository;
    private final UserService userService;

    public UserApplicationService(IUserRepository userRepository, UserService userService) {
        this.userRepository = userRepository;
        this.userService = userService;
    }

    public void update(UserUpdateCommand command) {
        UserId targetId = new UserId(command.getId());
        User user = userRepository.find(targetId);

        if (user == null) {
            throw new UserNotFoundException(targetId);
        }

        String name = command.getName();
        if (name != null) {
            UserName newUserName = new UserName(name);
            user.changeName(newUserName);

            if (userService.exists(user)) {
                throw new CanNotRegisterUserException(user, "이미 등록된 사용자입니다.");
            }
        }

        String mailAddress = command.getMailAddress();
        if (mailAddress != null) {
            MailAddress newMailAddress = new MailAddress(mailAddress);
            user.changeMailAddress(newMailAddress);
        }

        userRepository.save(user);
    }
}

 

이처럼 커맨드 객체는 파사드 역할을 한다.

 

 


 

 

요약

 

1. 어플리케이션 서비스를 구현할 때는 Entity를 직접적으로 드러내기 보다 DTO를 활용해야 한다.

2. 조회의 결과를 DTO로 표현할 시 데이터에 대한 수정을 막을 수 있다.

3. 수정할 데이터를 DTO로 표현할 시 수정 메소드의 시그니처를 하나로 통일할 수 있다.