티스토리 뷰
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+_.-]+@(.+)$");
}
}'라이브러리&프레임워크 > Spring' 카테고리의 다른 글
| Bounded Context란 무엇인가? (0) | 2026.03.01 |
|---|---|
| API 개발 시 중복 요청을 고려해본 적이 있나요? (0) | 2025.11.23 |
| Spring 개발자를 위한 캐싱 전략: 로컬 캐시부터 Redis 분산 캐시까지 (0) | 2025.07.27 |
| Spring Cache 사용법 정리 (0) | 2025.07.27 |
| Spring Boot Metric 수집 및 시각화 방법 (Prometheus + Grafana) (0) | 2025.07.20 |
