📌 개요
JPA로 프로젝트를 하면서 Lombok의 @Builder를 활용해 Builder 패턴으로 객체를 생성하기로 결정했다. 하지만 생성자는 Lombok을 쓰지 않고 직접 코드를 적는다. 이렇게 결정한 이유는 다음 2가지 문제 때문이다.
- 생성자로 객체 생성 시 생기는 문제
- @AllArgsConstructor 사용 시 생기는 문제
✅ 생성자로 객체 생성시 생기는 문제
🚨 가독성 하락
함수에서 이상적인 인수 개수는 0개다. 다음은 1개고, 다음은 2개다. 3개는 가능한 피하는 편이 좋다. 4개 이상은 특별한 이유가 필요하다. - 클린코드
클린코드에서 함수의 인자 개수는 적어야 한다고 한다. 함수의 인자가 많으면 순서가 생기게 되고, 의미 전달이 불분명해진다. Junit의 assertEqual() 함수에 expected가 첫 번째인지 두 번째 인지 헷갈리는 것처럼 말이다. 자바 개발을 하면서 생성자를 만들면 이런 비슷한 경험을 하게 된다.
Member member = new Member(1L,"유저1","aaa@naver.com");
이 코드는 id, 닉네임, 이메일 순으로 매개변수를 넣었다. 이 경우 하드코딩된 문자열을 보면서 닉네임, 이메일이 유추는 되겠지만, 작성자는 '닉네임이 먼저였나?'라는 고민을 하게 된다. 또 이메일처럼 특정한 문자열이 아니었다면 무슨 뜻인지 읽는 사람도 고민을 하게 된다.
🚨 뜨지 않는 컴파일 에러
public Member(Long id, String email, String nickname) {
this.id = id;
this.email = email;
this.nickname = nickname;
}
Member member = new Member(1L,"유저1","aaa@naver.com");
생성자는 id, email, nickname 순으로 코드를 작성했지만, 객체 생성시 id, nickname, email로 넣었다. 타입을 기준으로 인식하기 때문에 컴파일 에러가 발생하지 않는다. 이는 더 큰 버그의 원인이 된다.
🛠️ Builder 패턴
이러한 문제를 해결하기 위해 Builder 패턴을 사용하기로 결정했다. Builder 패턴은 1개의 매개변수당 1개의 메서드로 값을 주입하고, 마지막에 통합 빌드하여 객체를 생성하는 방식이다. 생성자를 이용한 방식과 Builder 패턴을 이용한 방식을 보자.
//생성자 방식
Member member1 = new Member(1L,"유저1","aaa@naver.com");
//Builder 적용
Member member2 = Member.builder().nickname("유저1").email("aaa@naver.com").build();
Builder 패턴 적용 시 메서드마다 이름이 붙어 어떤 값이 들어간 것인지 명확히 알 수 있다.
✅ AllArgsConstructor 사용 시 생기는 문제
Builder 패턴을 사용하고 싶다면 직접 구현할 수도 있지만 Lombok을 쓰면 편하다. @Builder를 쓰려면 매개변수가 있는 생성자가 있어야 한다. 만약 생성자를 만들지 않는다면 @AllArgsConstructor를 자동으로 붙여서 Builder 패턴을 완성한다.
@Builder
public class Member{
...생략
}
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member extends BaseEntity{
@Id
private Long id;
private String nickname;
private String email;
}
여기서 문제가 발생한다. 백엔드 개발을 하면서 JPA를 쓰려면 Entity에 기본생성자가 public 또는 protected를 가진 채로 존재해야 한다. @NoArgsConstructor를 붙여서 해결할 수 있지만, 이제 @Builder에는 에러가 뜬다. 생성자가 없다면 @AllArgsConstructor를 붙여서 @Builder가 자동으로 생성자를 만들지만, 이제는 NoArgsConstructor가 있기 때문에 기본 생성자만 존재하기 때문이다.
여기서 해결책은 2가지다.
- @AllArgsConstructor를 이용해서 생성자를 만든다.
- 매개변수를 가진 생성자를 직접 만든다.
@AllArgsConstructor를 하면 class가 가진 모든 멤버변수를 이용한 생성자가 만들어지고, Builder 패턴 적용 시 모든 멤버변수를 설정할 수 있게 된다. 하지만 모든 멤버변수를 넣게 되면, 건드리면 안 되는 값까지 건들수가 있다. 예를 들어 id의 경우 JPA에서 보통 자동 생성 전략을 취한다. 즉 개발자가 직접 넣어주면 안되는 값이다. @AllArgsConstructor 사용 시 id도 Builder패턴으로 생성이 가능하게 됨으로 이런 것들은 미리 막아주는 것이 좋다. 따라서 나는 생성자를 직접 만들고 그 위에 @Builder를 적어 Builder 패턴을 적용하기로 했다.
📝 결론
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity{
@Id
private Long id;
private String nickname;
private String email;
@Builder
public Member(String email, String nickname) {
this.email = email;
this.nickname = nickname;
}
}
'개발' 카테고리의 다른 글
| Spring Security + 세션 + 소셜로그인 (0) | 2025.01.08 |
|---|---|
| CORS 에러가 발생하는 과정 (0) | 2025.01.03 |
| React Native WebView로 PortOne 결제 API 연동하기 (0) | 2024.12.04 |
| Git pull rebase, merge 차이 (0) | 2024.12.03 |
| 비동기와 싱글스레드 (1) | 2024.12.03 |