문제 상황
Response DTO를 어떻게 하면 불변한 클래스로 구성할 수 있을까 고민해봤다.
우선 결론부터 말하면, 현재 프로젝트에서의 Response DTO를 다음과 같이 구성했다.
@Getter
@Builder(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class UserProfileResponse {
private final Long id; //유저 id
private final String email; //유저 email
//유저 엔티티를 인자로 하여 ResponseDTO를 반환하는 정적 팩토리메서드
public static UserProfileResponse of(User user){
return UserProfileResponse.builder() //builder 호출
.id(user.getId())
.email(user.getEmail())
.build();
}
}
1. 직렬화 같은 경우는 역직렬화와 다르게 기본 생성자가 필요가 없다.
2. 엔티티를 인자로 하여 ResponseDTO를 반환하는 정적 팩토리 메서드를 사용한다.
3. 정적 팩토리메서드에서의 변환은 lomok의 @builder를 사용한다.
4. 변수가 모두 필수적이기 때문에 @builder를 클래스 위에 적용하여 모든 변수를 인자로 하는 생성자를 호출하게 한다. (아래에서 설명하지만 해당 클래스에는 별도의 생성자를 만들지 않았기 때문에 이는 @builder가 자동으로 만들어준다.)
불변 클래스를 만들기 위해 생각한 목표
1. 접근 가능성 최소화
정적 팩토리 메서드를 제외한 접근은 ResponseDTO 클래스 외부에서는 접근이 불가능하게 할 생각이다.
private한 생성자 + public한 정적 팩토리 메서드를 사용하여 사실상 final인 불변 클래스를 만들려고 하며, 이는 클래스 외부에서 해당 클래스의 상속, 확장이 불가능하다.
이를 위해 ResponseDTO 객체를 생성할 수 있는 방법을 확인하여 필요한 경우를 제외한 경우는 모두 최소화하였다.
현재 ResponseDTO 객체를 생성할 수 있는 방법은 다음과 같다.
1.1) 정적 팩토리 메서드 : public
public UserProfileResponse getUserProfile(){
User user = userRepository.findById(userId)
.orElseThrow(() -> UserNotFoundException.EXCEPTION);
return UserProfileResponse.of(user); //정적 팩토리 메서드 사용
}
클래스 외부에서 유일하게 객체 생성이 가능한 방법이다.
보통 Service 계층에서의 엔티티 => DTO 변환하는 목적으로 호출하여 사용하기 때문에 public으로 설정하여 접근 가능하게 하였다.
1.2) @builder : @Builder(access = AccessLevel.PRIVATE)
ResponseDTO의 객체 생성은 @builder을 이용하여 만들어질 수 있다. 이는 현재 정적 팩토리 메서드 내부에서 호출이 이루어지며, 다른 곳에서 호출할 필요성은 없기 때문에, builder 메서드의 접근 제어자를 private하게 설정하기 했다.
1.3) 생성자 : @AllArgsConstructor(access = AccessLevel.PRIVATE)
@builder(builder 메서드)를 private하게 막아놓아도 lombok의 @builder을 사용 시 별도의 생성자를 만들지 않는다면, 내부 생성자는 모든 변수를 인자로 하는 package-private한 생성자로 만들어진다. 이를 private하게 만들어 같은 클래스 내 builder 메서드에서만 생성자를 호출할 수 있게 하였다.
2. 필드 불변 : private final
ResponseDTO는 정적 팩토리 메서드를 이용한 객체 생성 이후 값이 달라질 필요가 없기 때문에 모든 필드값을 private final로 설정하여 값의 불변성을 유지하였다.
private final Long id; //유저 id
private final String email; //유저 email
추후 고려할 상황
1. 테스트 코드
테스트 코드 작성 시 해당 클래스의 접근이 불가능할 수도 있다. 추후에 이를 고려하여 테스트 코드 구성 및 Response DTO 구성을 검토해볼 생각이다.
2. Record 사용
Java 17 이후의 Record를 사용하여 보일러플레이트를 최소화화여 불변한 ResponseDTO를 만들어볼 생각이다.
참고
[Effective Java] 아이템 1. 생성자 대신 정적 팩토리 메서드를 고려하라
[Effective Java] 아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라
[Effective Java] 아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라
[Effective Java] 아이템 15. 클래스와 멤버의 접근 권한을 최소화하라
[Effective Java] 아이템16 public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
[Effective Java] 아이템 17. 변경 가능성을 최소화하라
'Spring' 카테고리의 다른 글
[Spring MVC] Spring Interceptor에 대해 알아보자 (0) | 2023.07.26 |
---|