프로젝트를 진행하며 Member에 대한 CRUD를 구현하던 중, 가장 고민이 됐던 부분은 Update라고 말할 수 있다.
1. Setter방식, 과연 이대로 괜찮은가?
처음에는 아래와 같이 Member Entity의 @Setter를 열어둔 뒤, update시에 Dto를 통해 받아온 값들을 Entity에 각각 SetXXX해주는 방식을 사용했었다.
@Transactional
public Member updateMember(Member member) {
Member existingMember = getMemberByEmail(member.getEmail());
existingMember.setEmail(member.getEmail());
existingMember.setPassword(member.getPassword());
existingMember.setNickname(member.getNickname());
existingMember.setPostcode(member.getPostcode());
existingMember.setAddress(member.getAddress());
existingMember.setDetailAddress(member.getDetailAddress());
existingMember.setPhone(member.getPhone());
existingMember.setRealName(member.getRealName());
return existingMember;
}
해당 방식으로 개발을 진행하던 와중 생긴 의문점과 생각들이 있었다.
1. 과연 entity에 대해서 외부에서 값을 변경 할 수 있도록 setter를 열어주는것이 맞는 선택일까?
2. set을 하는 코드만을 보았을 때 이게 생성을 위한 건지, update를 위한 건지 명확하지가 않다(더불어서 보기도 좋지 않다..!)
그리고 Setter를 막기로 결심한 가장 마지막 원인은 Setter방식이 SOLID 법칙의 Open-Closed 법칙을 위반한다는 점이었다.
2. 그럼 어떤식으로 Entity를 Update 해줄 수 있지?
따라서, Member Entity에 update를 위한 메서드를 생성하 기려 했다. 물론 update함수를 외부에 열어둔다는 것 자체도 SOLID 법칙을 위반한다고 볼 수 있으나, 적어도 set방식을 통해 생성인지 혹은 수정인지 알 수 없는 개별의 변경 가능성을 열어두는 것보다 "MemberUpdate"라는 목적이 확실하게 드러나는 메서드명을 통해 변경 가능성을 열어주는 것이 더 나을 것이라고 판단했기 때문이다.
더불어서, null 값이 들어오는 경우 기존 내용이 날아가는것을 막기 위해 Optional의 ofNullable을 활용해 null값이 아닌 경우에만 해당 필드를 변경하도록 설정해 두었다. (@DynamicUpdate 등의 기능을 사용해보고자 하였으나 Dto활용 중인 현재 코드 상황과 Hybernate에서도 해당 기능이 Deprecated 되었다는 점을 확인하고 Optional을 통해 null값은 반영하지 않는 방향으로 구현했다. *추후에 해당 값을 완전히 삭제해야 하는 경우가 생기면 [needToDelete]등을 값으로 받아 null로 변환해, 완전히 삭제하는 update기능도 구현하고자 한다)
public void updateMember(Member updateMember){
Optional.ofNullable(updateMember.getEmail()).ifPresent(email -> this.email = email);
Optional.ofNullable(updateMember.getNickname()).ifPresent(nickname -> this.nickname = nickname);
Optional.ofNullable(updateMember.getPassword()).ifPresent(password -> this.password = password);
Optional.ofNullable(updateMember.getRealName()).ifPresent(realName -> this.realName = realName);
Optional.ofNullable(updateMember.getPhone()).ifPresent(phone -> this.phone = phone);
Optional.ofNullable(updateMember.getAddress()).ifPresent(address -> this.address = address);
}
3. 결론
결론적으로 update를 구현하는 방법을 Setter방식에서 Entity 내부 method 방식으로 변경하여, 도메인 계층인 Entity에 대해서는 @Setter을 영원히(?) 보내줄 수 있었다 :) 영속성 객체 생성시에도 빈 객체 생성 후 set해주는 방식이 아닌, @Builder와 Builder.default방식을 통해 보다 객체지향원칙을 지키는 Entity를 만들어 낼 수 있었다.
package yana.playground.member.entity;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import yana.playground.global.Auditable;
@Getter
@Builder
@Entity
@Table(name = "MEMBER")
@NoArgsConstructor
@AllArgsConstructor
public class Member extends Auditable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(length = 20)
private String nickname;
private String password;
private String realName;
@Column(nullable = false, unique = true)
private String phone;
@Enumerated(value = EnumType.STRING)
@Builder.Default
private MemberStatus status = MemberStatus.MEMBER_ACTIVE;
@Embedded
private Address address;
public void updateMember(Member updateMember){
Optional.ofNullable(updateMember.getEmail()).ifPresent(email -> this.email = email);
Optional.ofNullable(updateMember.getNickname()).ifPresent(nickname -> this.nickname = nickname);
Optional.ofNullable(updateMember.getPassword()).ifPresent(password -> this.password = password);
Optional.ofNullable(updateMember.getRealName()).ifPresent(realName -> this.realName = realName);
Optional.ofNullable(updateMember.getPhone()).ifPresent(phone -> this.phone = phone);
Optional.ofNullable(updateMember.getAddress()).ifPresent(address -> this.address = address);
}
public void deleteMember(Member deleteMember){
this.status = MemberStatus.MEMBER_WITHDRAWAL;
}
}
4..? 여담 "아! Setter 없애길 잘했다!"
현재 진행중인 Code Review 스터디에서 아니나 다를까 Entity의 Setter지양 권고에 대한 리뷰를 받았고, 수정해놨지요~(브이)를 할 수 있었다. Setter를 삭제하는게 과연 맞았을까 계속 고민해왔는데 조금은 확신이 더 생긴것 같다 :)
'Project > playground(java-spring,멀티모듈)' 카테고리의 다른 글
[playground] @Embedable, Dto, Mapstruct, Entity setter 그 사이의 고민들 (0) | 2023.07.03 |
---|---|
[playground] spring boot 멀티모듈 프로젝트 시작하기 (0) | 2023.06.25 |
야나의 코딩 일기장 :) #코딩블로그 #기술블로그 #코딩 #조금씩,꾸준히
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!