티스토리 뷰

1. 불변성

setter를 사용하면 애플리케이션 어느 계층에서든 엔티티 객체의 데이터를 자유롭게 변경할 수 있다. 이로 인해 의도치 않은 변경이 발생하여 데이터가 손상될 수 있다.

setter의 남용을 막아, 엔티티 생성 시점 이후 가능한 한 불변하게 유지해야 한다.
만약, 필드값을 변경해야 한다면 무분별한 setter 대신 의미가 명확한 메서드를 통해 변경하도록 설계한다.

// ❌ Setter가 있는 경우
@Service
public class OrderService {

    public void processOrder(User user, Order order) {
        // 주문 처리 중...

        // 다른 메서드 호출
        sendEmail(user, order);

        // 😱 user가 변경되었을 수도!
        // user.getEmail()이 원래 값이 아닐 수 있음
        log.info("주문 완료: {}", user.getEmail());
    }

    private void sendEmail(User user, Order order) {
        // 실수로 또는 의도적으로 변경
        user.setEmail("wrong@example.com");
        // 이메일 발송...
    }
}

위 예시에서 sendEmail() 메서드가 User 객체의 상태를 변경했지만, 이를 호출한 processOrder()는 그 사실을 알 수 없다.
이처럼 엔티티의 불변성이 보장되지 않으면, 데이터 변경의 추적이 어려워지고 디버깅 비용이 급격히 증가한다.

2. 불완전한 객체 생성 문제

setter 방식으로 객체를 생성하면 필수 필드 입력 누락이 발생할 수 있고, 생성자 방식은 인자 순서를 외워야 하는 불편함이 있다. 특히 동일 타입 인자가 여러 개일 경우, 순서가 바뀌어도 컴파일 오류 없이 잘못된 객체가 생성될 수 있다.

Builder 패턴은 이러한 문제를 해결한다.

// ❌ password는 깜빡함! 😱 불완전한 객체
User user = new User();
user.setEmail("user@example.com");
user.setName("홍길동");

// ❌ 생성자는 파라미터 순서 헷갈림
User user = new User("user@example.com", "홍길동", "encryptedPassword");
User user = new User("홍길동", "user@example.com", "encryptedPassword"); 

3. 명확하고 가독성 좋은 생성 방식

Builder 패턴은 생성자보다 어떤 필드가 어떤 값으로 설정되는지 명시적으로 보여주기 때문에
코드의 의도가 명확하고 유지보수가 용이하다.

User user = new User("홍길동", "user@example.com", "encryptedPassword"); 

// ✅ 무엇을 설정하는지 명확
User user = User.builder()
    .email("user@example.com")
    .name("홍길동")
    .password("encryptedPassword")
    .build();

4. 비즈니스 로직 누락

setter를 통해 필드에 직접 값을 주입하면 검증·암호화·로깅 등 비즈니스 로직이 누락될 위험이 있다. 특히 엔티티가 도메인 규칙을 지켜야 하는 DDD 관점에서는 setter 사용이 이런 규칙을 우회하는 진입로가 된다. 따라서 변경해야하는 필드가 존재하는 경우 필드값을 검증할 수 있는 메서드·비즈니스 의미가 드러나는 메서드를 따로 제공해야한다.

// ❌ Setter 직접 호출
User user = userRepository.findById(1L);
user.setPassword("newPassword");  // 😱 암호화 안됨!
userRepository.save(user);

// ❌ 검증 로직 누락
user.setEmail("invalid-email");  // 😱 형식 검증 안됨!
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;
    private String name;
    private String password;

    @Builder
    private User(String email, String name, String password) {
        this.email = email;
        this.name = name;
        this.password = password;
    }

    // ✅ 비즈니스 의미가 명확한 메서드
    public void changePassword(String newPassword, PasswordEncoder encoder) {
        // 검증 로직
        if (newPassword == null || newPassword.length() < 8) {
            throw new IllegalArgumentException("비밀번호는 8자 이상이어야 합니다");
        }

        // 암호화
        this.password = encoder.encode(newPassword);
    }

    // ✅ 프로필 수정
    public void updateProfile(String name, String phoneNumber) {
        if (name != null && !name.isBlank()) {
            this.name = name;
        }
        if (phoneNumber != null) {
            this.phoneNumber = phoneNumber;
        }
    }

    // ✅ 이메일 변경 (검증 포함)
    public void changeEmail(String newEmail) {
        if (!isValidEmail(newEmail)) {
            throw new IllegalArgumentException("유효하지 않은 이메일 형식입니다");
        }
        this.email = newEmail;
    }

    private boolean isValidEmail(String email) {
        return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
    }
}
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함