Builder Pattern
백엔드 개발 시 Entity나 Dto와 같은 객체를 생성할 때 사용할 수 있는 방법은 크게 세 가지가 있다.
- 생성자
- Setter
- Builder
Builder 패턴 사용 권장 이유
만약 백엔드 웹개발을 처음 배운다면 생성자 혹은 Setter를 사용하는 것이 일반적일 것이고 나 역시도 그랬었다. 그러나 이 두 방법들은 각각 단점을 가지고 있다. 생성자 패턴의 경우 객체에 넣어야 할 인자가 많을 경우 몇 번째 인자에 어떤 데이터를 넣어야 하는지 헷갈릴 수도 있다. 나 역시 이전 프로젝트에서 생성자 패턴을 주로 사용했었는데 주의하지 않으면 엉뚱한 데이터가 들어가기도 하고, 넣어야 하는 인자 보다 적거나 많게 넣어 컴파일 에러가 발생하기도 하였다. Setter를 사용한 방법은 setId() 와 같이 넣어야 하는 인자가 변수명으로 명시되어 있기 때문에 어떤 값을 객체에 넣었는지 직관적으로 확인할 수 있기에 생성자 패턴의 단점을 보완할 수 있다. 그러나 Setter 방식은 Entity와 같은 객체의 경우 setter를 남발하게될 경우 비지니스로직 내에서 본래 데이터가 아닌 수정된 값이 들어갈 수 있는 위험이 있다. 이는 수정에 닫혀있어야 한다는 객체지향원리인 OCP(Open-Closed Principle)을 위반하게 된다. 추가로 craete, update 등과 같은 모든 작업을 setter으로 하게 된다면 해당 메서드가 무슨 역할을 하는지 직관적으로 알기 쉽지 않다.
이 중 Builder 패턴은 다음과 같은 장점을 갖는다.
- 필요한 데이터만 설정할 수 있다.
- 유연성을 확보할 수 있다.
- 가독성을 높일 수 있다.
- 변경 가능성을 최소화 할 수 있다.
빌더패턴의 장점 관련 잘 정리한 글이 있어 링크를 남겨둔다.
https://mangkyu.tistory.com/163
Builder 패턴을 사용하는 방식은 별도의 class를 생성하는 방식과 Lombok 라이브러리에 내장된 @Builder 어노테이션을 사용하는 방식이 있는데, 어토네이션을 사용하는 방법에 대해 간단히 정리해보겠다.
어노테이션을 사용한 @Builder 패턴 사용법
- 빌더패턴을 사용하고자 하는 클래스에 @Builder 명시.
- 해당 객체를 빌더패턴으로 생성시, 클래스명.builder().전달받은데이터.build(); 형식으로 사용.
우선 빌더패턴으로 객체를 생성하고자 하는 클래스에 @Builder 어노테이션을 명시해준다. 그리고 해당 객체를 생성하고자 하는 위치에서 static 메서드인 builder()을 사용한다.
클래스명.builder().전달받은데이터.build();
// reqDto는 브라우저에서 입력받은 데이터를 담은 DTO 객체이다.
SiteUser siteUser = SiteUser.builder()
.id(reqDto.getId())
.name(reqDto.getName())
.build();
그런데 만약 빌더패턴을 Entity에 적용하고자 한다면 기본생성자 어노테이션 @NoArgsConstructor와 모든 필드를 인자로 받는 생성자 @AllArgsConstructor을 추가해주어야 한다.
Entity의 경우 Reflection API를 활용하기 때문에 기본 생성자가 반드시 필요한 반면, @Builder의 경우 필요한 모든 필드에 대한 값을 인자로 받는 생성자가 필요하고 서로 충돌이 발생한다고 한다.
따라서 Entity에 빌더패턴을 적용하기 위해서는 @AllArgsConstructor와 @NoArgsConstructor을 사용해 주어야 한다.
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SiteUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String uid;
private String name;
}
@Service
public class UserApiService {
public UserCreateResDto create(UserCreateReqDto reqDto) {
SiteUser siteUser = SiteUser.builder()
.uid(reqDto.getUid())
.name(reqDto.getName())
.build();
/* 생략 */
return resDto;
}
}