왜 Builder 패턴을 사용해야하는가?
전체 코드는 Github에서 볼 수 있습니다.
Builder 패턴을 사용하면 다음과 같은 장점이 있습니다.
- 인자가 많을 경우 쉽고 안전하게 객체를 생성할 수 있습니다.
- 인자의 순서와 상관없이 객체를 생성할 수 있습니다.
- 적절한 책임을 이름에 부여하여 가독성을 높일 수 있습니다
Entity 설계시 어떤 어노테이션을 활용해야하고 왜 활용해야하는지 모르겠다면 아래 글을 참고하면 좋을듯하다.!!
[Spring] Entity를 설계할때 주의할점!! 어노테이션 뭘 써야하고 왜 쓰는거야???
글에서 나온 코드는 Github에서 확인 할 수 있습니다. 최근에 Spring을 시작하고 Entity를 설계하면서 아래와 같이 어노테이션을 활용하여 설계를 하였는데,문득 궁금해지기도하고 올바른 설계가 맞
dentuniverse.tistory.com
1. 불안전한 객체 생성 패턴
최근 Python에서 Java로 넘어오면서 Builder를 작성할때 아래와 같이 작성을 하곤 했다. 우선 데이터베이스의 칼럼이 not null인 경우에는 대부분의 엔티티의 멤버실의 값도 null이면 안된다. 그 뜻은 해당 객체를 생성할 경우에도 동일합니다.
@Entity
@Table(name = "account")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class AccountEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@NotEmpty
@Column(name = "bank_name", nullable = false)
private String bankName;
@NotEmpty
@Column(name = "account_number", nullable = false)
private String accountNumber;
@NotEmpty
@Column(name = "account_holder", nullable = false)
private String accountHolder;
// 불안전한 객채 생성 패턴
@Builder
public AccountEntity(String bankName, String accountNumber, String accountHolder) {
this.bankName = bankName;
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
}
@Override
public String toString() {
return "AccountEntity{" +
"id=" + id +
", bankName='" + bankName + '\'' +
", accountNumber='" + accountNumber + '\'' +
", accountHolder='" + accountHolder + '\'' +
'}';
}
}
위 코드를 테스트 코드로 작성했을때를 확인해보자.
Test Code
@Test
@DisplayName(value = "불안전한 객채 생성 패턴")
void createUnstableAccountEntity() {
AccountEntity accountEntity = AccountEntity.builder()
.accountHolder("")
.accountNumber(ACCOUNTNUMBER)
.bankName(BANKNAME)
.build();
assertThat(accountEntity.getBankName()).isEqualTo(BANKNAME);
assertThat(accountEntity.getAccountNumber()).isEqualTo(ACCOUNTNUMBER);
System.out.println("AccountEntity 객체 : " + accountEntity.toString());
}
이렇게 작성했을때, 빌더 내부에 유효성 검사를 강제하지 않으면, Entity에선 @NotEmpty라고 선언한 필드의 값들에 Null값도 들어가서 에러의 발생이 뒷단으로 넘어갈수도 있게된다.
때문에 @Builder 자체에서 유효성 검사를 넣는것이 더 좋다고 생각이 된다.
2. 안전한 객체 생성 패턴
아래 코드는 해당 @Builder에 유효성을 주입하여, Null 체크를 하는 검사를 진행한다.
@Entity
@Table(name = "account")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class AccountEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@NotEmpty
@Column(name = "bank_name", nullable = false)
private String bankName;
@NotEmpty
@Column(name = "account_number", nullable = false)
private String accountNumber;
@NotEmpty
@Column(name = "account_holder", nullable = false)
private String accountHolder;
// 안전한 객채 생성 패턴
@Builder
public AccountEntity(String bankName, String accountNumber, String accountHolder) {
Assert.hasText(bankName, "bankName 값이 누락되었습니다.");
Assert.hasText(accountNumber, "accountNumber 값이 누락되었습니다.");
Assert.hasText(accountHolder, "accountHolder 값이 누락되었습니다.");
this.bankName = bankName;
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
}
@Override
public String toString() {
return "AccountEntity{" +
"id=" + id +
", bankName='" + bankName + '\'' +
", accountNumber='" + accountNumber + '\'' +
", accountHolder='" + accountHolder + '\'' +
'}';
}
}
이렇게 작성했을때, 우리가 의도하는 Null값에 대한 유효성을 주입하여, @NotEmpty로 선언한 필드들에 대해서 안정성도 보장된다.
Test Code
@Test
@DisplayName(value = "안정적인 객채 생성 패턴")
void createstableAccountEntity() {
AccountEntity accountEntity = AccountEntity.builder()
.accountHolder("")
.accountNumber(ACCOUNTNUMBER)
.bankName(BANKNAME)
.build();
assertThat(accountEntity.getBankName()).isEqualTo(BANKNAME);
assertThat(accountEntity.getAccountNumber()).isEqualTo(ACCOUNTNUMBER);
System.out.println("AccountEntity 객체 : " + accountEntity.toString());
}
여기서 조금 더 나아가서 지금까지 Builder를 사용할땐, 아래와 같이 사용했는데,,,,,,알아보니 좀 더 직관적으로 사용할 수 있는 방법이 있었다.
.builder().build();의 방식으로 사용하는것이 아니라 builder()에 이름을 부여할 수 있다는 사실,,,!!
AccountEntity accountEntity = AccountEntity.builder()
.accountHolder("")
.accountNumber(ACCOUNTNUMBER)
.bankName(BANKNAME)
.build();
방법은 아래와 같다
3. 안정적인 객체 생성 + 빌더 이름 부여
@Builder(builderClassName = "CreditAccountBuilder", builderMethodName = "creditAccountBuilder")
public AccountEntity(String bankName, String accountNumber, String accountHolder) {
Assert.hasText(bankName, "bankName 값이 누락되었습니다.");
Assert.hasText(accountNumber, "accountNumber 값이 누락되었습니다.");
Assert.hasText(accountHolder, "accountHolder 값이 누락되었습니다.");
this.bankName = bankName;
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
}
여기저기서 글을 읽을때 builderClassName, builderMethodName을 한쌍으로 반드시 선언해줘야만 작동을 하는줄 알았는데, builderMethodName만 선언해줘도 작동을 하였다,,,
이유는 이러하였다.
@Builder 어노테이션을 사용할 때 builderClassName을 생략하고 builderMethodName만 지정하면, Lombok은 기본적으로 클래스를 위한 빌더 클래스를 생성하고, 지정된 메서드 이름을 사용하여 빌더 인스턴스를 반환한다.
Lombok은 기본 빌더 클래스를 <클래스 이름>Builder 형식으로 생성하고, creditAccountBuilder라는 정적 메서드를 통해 빌더 인스턴스를 반환하였다.
한번 더 수정한 안정적인 객체 생성 + 빌더 이름 부여 코드는 아래와 같다.
@Builder(builderMethodName = "creditAccountBuilder")
public AccountEntity(String bankName, String accountNumber, String accountHolder) {
Assert.hasText(bankName, "bankName 값이 누락되었습니다.");
Assert.hasText(accountNumber, "accountNumber 값이 누락되었습니다.");
Assert.hasText(accountHolder, "accountHolder 값이 누락되었습니다.");
this.bankName = bankName;
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
}
이제 마지막으로 테스트 코드를 작성해보고 이번 글을 마무리하려고 한다.
Test Code
중점적으로 볼 코드는, AccountEntity.builder() -> AccountEntity.creditAccoutBuilder()로 변경되었다는 부분이다.
@Test
@DisplayName(value = "안정적인 객채 생성 빌더 이름 명명")
void createAccountEntityBuilderName() {
AccountEntity accountEntity = AccountEntity.creditAccountBuilder()
.accountHolder("")
.accountNumber(ACCOUNTNUMBER)
.bankName(BANKNAME)
.build();
assertThat(accountEntity.getBankName()).isEqualTo(BANKNAME);
assertThat(accountEntity.getAccountNumber()).isEqualTo(ACCOUNTNUMBER);
System.out.println("AccountEntity 객체 : " + accountEntity.toString());
}
테스트 결과는 과연??? 두구두구!!
생략해도 유효성검사가 잘 되는것을 확인하였다.
오늘의 회고
기존에 Python+Django, FastAPI를 주로 사용하다가 Java + Spring으로 넘어오려니,,,,,이것저것 공부할것들이 많아져 재미도 있지만 다시 바빠져 앞으로 계속 글을 작성하고 굉장히 초보적인 글들도 많이 적게 될거같다,,,,🥲🥲🥲🥲🥲
'프로그래밍 > Spring' 카테고리의 다른 글
[Spring] Entity를 설계할때 주의할점!! 어노테이션 뭘 써야하고 왜 쓰는거야??? (7) | 2024.11.06 |
---|