[오늘의 배움] JPA 엔티티 필드 검증과 객체 생성

이상민·2021년 10월 7일
0

[오늘의 배움]

목록 보기
63/70
post-thumbnail

아래 간단한 엔티티에 대해서 피드백 받고 생각해본점을 정리했다

// Customer.java

@Entity
@Table(name = "customers")
@Getter
public class Customer {
    @Id
    private Long id;
    private String firstName;
    private String lastName;

    public void setId(Long id) {
        validateId(id);
        this.id = id;
    }

    public void setFirstName(String firstName) {
        validateName(firstName);
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        validateName(lastName);
        this.lastName = lastName;
    }

    private void validateId(Long id) {
        if (id < 0)
            throw new IllegalArgumentException("ID cannot be a negative number");
    }

    private void validateName(String name) {
        final int MAX_NAME_LENGTH = 255;

        if (name.contains(" "))
            throw new IllegalArgumentException("Name cannot contain space");

        if (name.length() > MAX_NAME_LENGTH)
            throw new IllegalArgumentException(MessageFormat.format("Name length cannot exceed {0} characters", MAX_NAME_LENGTH));
    }
}

엔티티 필드의 검증

엔티티에 들어갈 데이터를 검증하는 방법에는 여러가지가 있다. 타입화할 수도 있고, 세터에서 검증할 수도 있고, 별도 검증 클래스가 있을 수도 있고, DTO에서 할 수도 있다. 어디서 하는게 좋을까?

타입화

이름을 추가한다 할때 Name이라는 타입을 만드는게 나을까?

  • 아래처럼 사용할 수 있다. 하지만 하나의 필드를 갖는 객체의 존재가 필요한지 잘 생각해봐야한다
class Name {
   String name;
   
   public Name(String name) {
       validate(name);
       this.name = name;
   }
}

Name firstName;
Name lastName;
  • 성 이름을 모두 관리할 수도 있다. 하지만 엔티티는 DB 테이블과 직접적으로 매핑된 객체인데 테이블의 구조를 보기 어렵게 숨기게되지는 않을까?
class Name {
   String firstName;
   String lastName;
   
   public Name(String firstName, String lastName) {
       validate(firstName, lastName);
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

Name name;
  • 애초에 엔티티의 레이어에서 검증하는게 맞을까? 보다 상위 레이어에서 검증하고 넘겨줘야하는것 아닐까?

세터 검증

validateName() 같은 검증 메소드를 엔티티에 작성하는게 나을까?

  • 맨위에 내가 작성엔 코드에서 했듯이 엔티티의 세터 메소드들에서 검증을 할 수도 있다

  • 하지만 세터 메소드에 검증 로직이 포함되는것이 맞을까?

  • 위 타입화와 마찬가지로 레이어 문제가 있다


별도 검증 객체

별도 Validator 객체를 작성하는게 나을까?

class Validator {

    public static void validateName(String name) {
        if (!패턴)
            throw new ...
    }

}
  • 레이어와 분리되어있기 때문에, 원하는 곳에서 사용할 수 있다. 하지만 이러한 검증기가 많아지면 정적 메소드들이 늘어난다는 점과 commons 같은 별도의 패키지를 관리해줘야한다

  • 필드가 몇개 없는 객체의 검증이라면 에너테이션을 작성해 볼 수도 있다


DTO 검증

엔티티에 도달하기 전의 DTO에서 검증하는게 나을까?

class CustomerDto {
    private final Long id;
    private final String firstName;
    private final String lastName;
    
    public CustomerDto (Long id, String firstName, String lastName) {
        validate(id)
        this.id = id;
        validate(firstName)
        this.firstName = firstName;
        validate(lastName)
        this.lastName = lastName;
    }
    
    ...

}
  • 레이어의 이동 전에 검증하기 때문에 오류 검출에 더 유리할 수 있다

  • 검증은 레이어 간 데이터 이동을 위한 DTO의 목적에 부합하기도 하다


결론

  • 검증 방식과 위치에 따라 장단점이 있을 수 있다

  • 왜 해당 위체에서 검증을 하는지 꼭 생각해보자

  • 검증 방식을 반드시 하나만 골라야하는 것은 아니다. 검증을 여러 레이어에서 해야 더욱 데이터의 상태가 안전해진다


엔티티의 생성

다른 팀원들 중 Customer 엔티티에 빌더 패턴을 적용한 분들도 있었다. 해당 엔티티에 생성은 어떻게 해볼 수 있을까?

빌더 패턴

빌더 패턴을 통해 생성하는게 나을까?

Customer customer = Customer.builder()
        .id(1L)
        .firstName("Sangmin")
        .lastName("Lee")
        .build;
  • 빌더 패턴이 필요한지 생각해보자

    • 보통 필드가 4개가 넘어가면 빌더 패턴을 고려한다고 한다

    • 위 경우에는 3개의 필드를 가지는데 그냥 생성자를 사용해도 괜찮지 않을까?


생성자

생성자를 사용하는게 나을까?

// 훨씬 짧고 간결하다 
Customer customer = new Customer(1L, "Sangmin", "Lee");
  • 인자 수가 적다면, 동일한 객체를 만들때 그냥 생성자를 사용하는게 더 간결할 수 있다

  • 정말 팩토리 패턴이 필요한지 생각해보자


+보너스 정적 팩토리 메소드

생성자 대신 정적 팩토리 메소드 사용을 고려해 볼 수도 있다

  • 정적 팩토리 메소드는 생성자에 비해 다음과 같은 장점을 가진다

    • 유의미한 메소드명
    • 자기 클래스 이외의 타입 반환 가능
    • 객체의 생성과 생성 중 로직 분리
  • String.valueOf가 대표적인 표준 자바의 정적 팩토리 메소드이다

// 당연히 아무도 이렇게 안쓰지만 이렇게 쓰는게 가능하긴 하다
String name = new String("Sangmin");

// 정적 팩토리를 통해 다양한 로직을 명확하게 실행할 수 있다 
String one = String.valueOf(1);
String bool = String.valueOf(true);
profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

0개의 댓글