네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

JPA를 이용하여 엔티티 객체들을 Builder 기반으로 생성하는 것은 흔한 패턴이다.

 

Builder로 안전하게 생성하자

JPA 엔티티 객체들을 Builder 기반으로 생성하는 것이 흔한 패턴이다. 

 

Builder 패턴의 장점

1. 인자가 많을 경우 쉽고 안전하게 객체를 생성할 수 있다.

2. 인자의 순서와 상관없이 객체를 생성할 수 있다.

3. 적절한 책임을 이름에 부여하여 가독성을 높일 수 있다.

 

@Embeddable
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Account {
    
    @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 Account(String bankName, String accountNumber, String accountHolder) {
        this.bankName = bankName;
        this.accountNumber = accountNumber;
        this.accountHolder = accountHolder;
    }
    
    // 안전한 객체 생성 패턴
    @Builder
    public Account(String bankName, String accountNumber, String accountHolder) {
        Assert.hasText(bankName, "bankName must not be empty");
        Assert.hasText(accountNumber, "accountNumber must not be empty");
        Assert.hasText(accountHolder, "accountHolder must not be empty");
        
        this.bankName = bankName;
        this.accountNumber = accountNumber;
        this.accountHolder = accountHolder;
    }
}

데이터베이스의 컬럼이 not null 인 경우에는 대부분 엔티티의 멤버값도 null이면 안된다. 

Account account = Account.builder().build();

불완전한 객체 생성 패턴으로 생성했을 경우 account 객체에는 모든 멤버 필드의 값이 null로 지정된다. account 객체로 추가적인 작업을 진행하면 NPE가 발생하게 된다.

안전한 객체 생성 패턴으로 생성했을 경우는 객체 생성이 Assert로 객체 생성이 진행되지 않는다. 필요한 값이 없는 상태에서 객체를 생성하면 이후 작업에서 예외가 발생하게 된다. 객체가 필요한 값이 없는 경우에는 적절하게 Exception을 발생시켜서 흐름을 종료하는 것이 맞다.

 

@Entity
@Table(name = "orders")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Order {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Embedded
  private Address address;

  @OneToMany(mappedBy = "order")
  private List<Product> products = new ArrayList<>();

  @Builder
  public Order(Address address, List<Product> products) {
    Assert.notNull(address, "address must not be null");
    Assert.notNull(products, "products must not be null");
    Assert.notEmpty(products, "products must not be empty");

    this.address = address;
    this.products = products;
  }
}

 

Builder 이름으로 책임을 부여하자

Builder의 이름을 명확하게 해서 책임을 부여하는 것이 좋다.

@Entity
@Table(name = "refund")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Refund {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Embedded
    private Account account;
    
    @Embedded
    private CreditCard creditCard;
    
    @OneToOne
    @JoinColumn(name = "order_id", nullable = false, updatable = false)
    private Order order;
    
    @Builder(builderClassName = "ByAccountBuilder", builderMethodName = "ByAccountBuilder")
    public Refund(Account account, Order order) {
        Assert.notNull(account, "account must not be null");
        Assert.notNull(order, "order must not be null");
        
        this.order = order;
        this.account = account;
    }
    
    @Builder(builderClassName = "ByCreditBuilder", builderMethodName = "ByCreditBuilder")
    public Refund(CreditCard creditCard, Order order) {
        Assert.notNull(creditCard, "creditCard must not be null");
        Assert.notNull(order, "order must not be null");
        
        this.order = order;
        this.creditCard = creditCard;
    }
}

빌더의 이름으로 책임을 명확하게 부여하고, 받아야 하는 인자도 명확하게 할 수 있다.

public class RefundTest {
  
  // 생략

  @Test
  public void ByAccountBuilder_test() {
    final Refund refund = Refund.ByAccountBuilder() // 빌더 이름
        .account(account)
        .order(order)
        .build();

    assertThat(refund.getAccount()).isEqualTo(account);
    assertThat(refund.getOrder()).isEqualTo(order);
  }

  @Test
  public void ByCreditBuilder_test() {
    final Refund refund = Refund.ByCreditBuilder() // 빌더 이름
        .creditCard(creditCard)
        .order(order)
        .build();

    assertThat(refund.getCreditCard()).isEqualTo(creditCard);
    assertThat(refund.getOrder()).isEqualTo(order);
  }

}

 

 출처 

https://cheese10yun.github.io/spring-builder-pattern/

 

Builder 기반으로 객체를 안전하게 생성하는 방법 - Yun Blog | 기술 블로그

Builder 기반으로 객체를 안전하게 생성하는 방법 - Yun Blog | 기술 블로그

cheese10yun.github.io

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !