실무에서는 Setter를 사용하지 않는다!
JAVA를 활용하여 Api를 개발하는 것을 경험해본 사람이라면 Setter를 사용하면 안된다는 말을 한 번쯤은 들어봤을 것입니다. 개발 초기에 Setter를 활용하여 값을 바인딩하였지만 이를 해결하기 위해 공부해봤습니다.
- Setter를 사용하여 개발을 하게되면, 다른 개발자가 어떠한 객체의 필드 값을 수정할 수 있습니다.
- OCP의 원리를 지킬 수 없습니다.
- 그리하여 Setter를 제거하고 builder 패턴을 적용하기 위해 팀원들에게 builder를 사용하는 이유를 설명하고, setter를 제거하는 과정을 거쳤으며 새로운 오류 또한 만날 수 있게 되었습니다.
@setter를 사용하여 객체를 생성하는 것과 @Builder를 사용하여 객체를 생성하는 것은 크게 다르지 않습니다.
setter + 기본 생성자 사용하여 객체 생성
Planner createPlanner = new Planner();
createPlanner.setPlanTitle(plannerCreateRequest.getPlanTitle());
createPlanner.setIsPrivate(plannerCreateRequest.getIsPrivate());
createPlanner.setMember(authUtil.getCurrentMember(request));
plannerRepository.save(createPlanner);
@Builder를 사용하여 객체 생성
Planner createPlanner = Planner.builder()
.planTitle(plannerCreateRequest.getPlanTitle())
.isPrivate(plannerCreateRequest.getIsPrivate())
.member(authUtil.getCurrentMember(request))
.build();
plannerRepository.save(createPlanner);
@Builder를 사용하는 가장 큰 이유는 .build() 를 할 경우 객체의 특정 필드를 수정할 수 없습니다.
다른 개발자가 혹시나 필드를 수정할 경우를 미리 방지하는 것입니다.
객체를 생성하는 것은 둘의 차이가 크지 않고 이해하기 쉽습니다.
그렇다면 수정은 어떻게 할 수 있을까요?
기존에 setter를 사용하여 특정 필드를 수정하면(따로 저장하지 않아도) JPA가 변경감지를 통해 알아서 저장해줍니다.
수정은 특정 필드만을 수정해야하는 것이기에 이작업이 까다롭습니다.
코드로 한 번 살펴봅시다.
공통로직
Long plannerId = plannerEditRequest.getPlannerId();
// 조회했을 때 플래너가 존재하지 않을 경우
Planner planner = plannerRepository.findById(plannerId)
.orElseThrow(() -> new ApiException(ErrorType.PAGE_NOT_FOUND));
// 플래너를 생성한 사람이 아닐 경우
if (!planner.getMember().getId().equals(currentMember.getId())) {
throw new ApiException(ErrorType.USER_NOT_AUTHORIZED);
}
DB에 없는 상황과 권한 설정을 확인 후에 작업을 시작합니다.
setter를 사용하여 수정
// 플래너 정보 수정
planner.setPlanTitle(plannerEditRequest.getNewPlanTitle());
planner.setIsPrivate(plannerEditRequest.getNewIsPrivate());
setter를 사용하여 특정 필드만을 수정한다면 매우 간단합니다.
이제 @Builder를 사용하여 수정을 진행해보겠습니다.
가장 먼저 해야할 작업은 Editor를 만드는 작업입니다. 값 필드를 어떻게 넣어줄지 정하는 클래스입니다.
이 Editor는 @Builder 대신, 직접 디자인 패턴 중 Builder 패턴대로 코드를 작성해주어야합니다.
Builder 패턴으로 수정
@Getter
public class PlannerEditor {
private String planTitle;
private Boolean isPrivate;
public PlannerEditor(String planTitle, Boolean isPrivate) {
this.planTitle = planTitle;
this.isPrivate = isPrivate;
}
public static PlannerEditorBuilder builder() {
return new PlannerEditorBuilder();
}
public static class PlannerEditorBuilder {
private String planTitle;
private Boolean isPrivate;
PlannerEditorBuilder() {
}
public PlannerEditorBuilder planTitle(final String planTitle) {
if (planTitle != null && !planTitle.isEmpty()) {
this.planTitle = planTitle;
}
return this;
}
public PlannerEditorBuilder isPrivate(final Boolean isPrivate) {
if (isPrivate != null) {
this.isPrivate = isPrivate;
}
return this;
}
public PlannerEditor build() {
return new PlannerEditor(planTitle, isPrivate);
}
}
}
그 후, 엔티티에서 PlannerEditor를 초기화하는 메서드를 생성합니다.
// Planner Entity
public PlannerEditor.PlannerEditorBuilder toEditor() {
return PlannerEditor.builder()
.planTitle(planTitle)
.isPrivate(isPrivate);
}
// set 메서드 내의 로직을 변경하는 메서드에서 사용
public void edit(PlannerEditor plannerEditor){
planTitle = plannerEditor.getPlanTitle();
isPrivate = plannerEditor.getIsPrivate();
}
이제 수정하는 Service에서 이들을 활용합니다.
// PlannerService
PlannerEditor.PlannerEditorBuilder editorBuilder = planner.toEditor();
PlannerEditor plannerEditor = editorBuilder
.planTitle(plannerEditRequest.getPlanTitle())
.isPrivate(plannerEditRequest.getIsPrivate())
.build();
planner.edit(plannerEditor);
수정할 수 있는 메서드, 즉 PlannerEditorBuilder를 새로 생성하여 editorBuilder로 초기화합니다.
PlannerEditorBuilder 타입인 editorBuilder에다가 수정하고자 하는 값들을 넣고 .build()를 사용하여 생성하여 PlannerEditorBuilder 타입에서 plannerEditor로 생성합니다.
그 후, Planner 엔티티 클래스에 있는 수정 메서드를 통해 값을 수정합니다.
여기서 알아둬야할 점은 PlannerEditor.PlannerEditorBuilder는 정적메서드이자 .build() 를 하지 않은 상태이기 때문에, 아직까진 수정할 수 있다는 점입니다.
이러한 Editor를 사용하면 setter를 제거하고 값을 수정, 삭제할 수 있습니다.