// 안전한 객채 생성 패턴 @Builder publicAccount(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");
우선 데이터베이스의 칼럼이 not null인 경우에는 대부분의 엔티티의 멤버실의 값도 null이면 안됩니다. 그 뜻은 해당 객체를 생성할 경우에도 동일합니다.
1
Account account = Account.builder().build(); // 불안전한 객체 생성 패턴으로 생성했을 경우
account 객체에는 모든 멤버 필드의 값이 null로 지정됩니다. 이것은 애초에 account 객체를 의도한 것처럼 생성되지 않은 경우입니다. account 객체로 추가적인 작업을 진행하면 NPE가 발생하게 됩니다.
1
Account account = Account.builder().build(); // 안전한 객체 생성 패턴으로 생성했을 경우
안전한 객체 생성 패턴으로 생성했을 경우는 객체 생성이 Assert으로 객체 생성이 진행되지 않습니다. 필요한 값이 없는 상태에서 객체를 생성하면 이후 작업에서 예외가 발생하게 됩니다. 그보다 객체가 필요한 값이 없는 경우에는 적절하게 Exception 발생시켜 흐름을 종료하는 게 좋다고 생각합니다. 이것은 우리가 컨트롤러에서 유효성 검사를 하는 이유와 동일합니다.
객체가 필요한 값이 없음에도 불과하고 이후 로직들을 진행하게 되면 더 비싼 비용이 발생합니다. 이미 트랜잭션이 시작했다거나, 해당 작업에 관련된 알림이 나갔다거나 등등이 있습니다.
Builder를 이용해서 객체를 생성하더라도 필수 값의 경우에는 반드시 그에 맞는 방어 코드를 작성하는 것이 좋다고 생각합니다.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Embedded private Address address;
@OneToMany(mappedBy = "order") private List<Product> products = new ArrayList<>();
@Builder publicOrder(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");
@Builder(builderClassName = "ByAccountBuilder", builderMethodName = "ByAccountBuilder") // 계좌 번호 기반 환불, Builder 이름을 부여해서 그에 따른 책임 부여, 그에 따른 필수 인자값 명확 publicRefund(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") // 신용 카드 기반 환불, Builder 이름을 부여해서 그에 따른 책임 부여, 그에 따른 필수 인자값 명확 publicRefund(CreditCard creditCard, Order order){ Assert.notNull(creditCard, "creditCard must not be null"); Assert.notNull(order, "order must not be null");
@Test publicvoidByAccountBuilder_test(){ final Refund refund = Refund.ByAccountBuilder() // 빌더 이름으로 명확하게 그 의도를 드러 내고 있습니다. .account(account) .order(order) .build();
@Test publicvoidByCreditBuilder_test(){ final Refund refund = Refund.ByCreditBuilder() // 빌더 이름으로 명확하게 그 의도를 드러 내고 있습니다. .creditCard(creditCard) .order(order) .build();
필수 값임에도 불과하고 Builder에서 충분히 검사를 하지 않으면 에러의 발생은 뒷단으로 넘어가게 됩니다. 최악의 경우에는 데이터베이스 insert 시 해당 값이 not null인 경우 데이터베이스에서 에러를 발생시키게 됩니다.
이렇게 되면 개발자에게 늦은 피드백을 주게 됩니다. 개발이 끝난 이후에 통합 테스트 구동 or 직접 스프링 구동해서 테스트 하는 방법은 에러에대한 피드백이 늦어지고 결국 생상성 저하로 이어진다고 생각합니다.
가능하다면 POJO 기반으로 빠르게 코드상으로 피드백을 받는 것이 좋다고 생각하고 있습니다.