흔히 프로그램을 작성하다보면, 사용자의 이름이나 나이 등을 원시타입(String, int) 등에 그대로 저장하는 경우가 있는데,
이런 경우에는 해당 값의 특성을 제대로 나타낼 수 없다.
이것은 여러 개발자가 함께 작업할 때 그 단점이 명확하게 나타나는데,
만약 String name 이라고 했을 때, 이름 값이 성과 이름을 포함하는 이름인지, 한국인 이름만 포함하는 이름인지 알 수 없기 때문에, db 를 조회해서 기존 data 가 어떤 식으로 저장되어 있는지 조회해야하는 등의 불편함이 있다.
따라서 원시타입을 그대로 사용하는 것이 아니라 해당 특성 값을 잘 나타낼 수 있도록 하면서,
동시에 유효성 검사도 진행할 수 있는 "값 객체"를 활용하는 것이 좋다.
값 객체를 도입했을 때의 장점
- 표현이 분명해진다.
- 무경성이 유지된다.
- 잘못된 대입을 방지한다.
- 유효성 검사 로직이 이곳저곳에 흩어지는 것을 방지한다.
게시판 생성 예시
실제로 Inhabas.com 웹페이지를 제작하면서, DDD 개념을 알기 전에 이런 작업을 했었다.
매번 값을 받아 저장 및 수정하기 전에 유효성 검사를 하기에는 너무 비효율적이었기 때문에
"해당 특성을 나타내는 객체를 만들어, 생성자 내부에서 유효성 검사를 진행하면 되겠다"는 생각을 해냈었다.
db의 길이 제한 값을 max length 로 지정해주어,
db 에서의 contraintException 이 발생하기 전에, 미리 잘못된 데이터를 걸러주도록 했다.
(물론 web 단에서의 bean validation 도 진행한다.)
// 게시판 제목 값 객체
@Embeddable
public class Title {
@Column(name = "title")
private String value;
@Transient
private static final int MAX_LENGTH = 100;
public Title() {}
public Title(String value) {
if (validate(value))
this.value = value;
else
throw new IllegalArgumentException();
}
private boolean validate(Object value) {
if (Objects.isNull(value)) return false;
if (!(value instanceof String)) return false;
String o = (String) value;
if (o.isBlank()) return false;
return o.length() < MAX_LENGTH;
}
public String getValue() {
return value;
}
}
// 게시글 내용 값 객체
@Embeddable
public class Contents {
@Column(name = "contents", columnDefinition = "MEDIUMTEXT")
private String value;
@Transient
private static final int MAX_SIZE = 2 << 24 - 1; //16MB
public Contents() {}
public Contents(String value) {
if (validate(value))
this.value = value;
else
throw new IllegalArgumentException();
}
private boolean validate(Object value) {
if (Objects.isNull(value)) return false;
if (!(value instanceof String)) return false;
String o = (String) value;
if (o.isBlank()) return false;
return o.length() < MAX_SIZE;
}
public String getValue() {
return value;
}
}
테스트 코드 예시
@DisplayName("Title 타입에 제목을 저장한다.")
@Test
public void Title_is_OK() {
//given
String titleString = "게시판 제목입니다.";
//when
Title title = new Title(titleString);
//then
assertThat(title.getValue()).isEqualTo("게시판 제목입니다.");
}
@DisplayName("Title 타입에 너무 긴 제목을 저장한다. 100자 이상")
@Test
public void Title_is_too_long() {
//given
String titleString = "지금이문장은10자임".repeat(10);
//then
assertThrows(IllegalArgumentException.class,
() -> new Title(titleString));
}
@DisplayName("제목은 null 일 수 없습니다.")
@Test
public void Title_cannot_be_Null() {
assertThrows(IllegalArgumentException.class,
() -> new Title(null));
}
@DisplayName("제목은 빈 문자열일 수 없습니다.")
@Test
public void Title_cannot_be_Blank() {
assertThrows(IllegalArgumentException.class,
() -> new Title("\t"));
}
이렇게 값 객체를 생성한 후에, 아래와 같이 게시글을 생성한다.
// 게시판 엔티티 생성자
public Board(String title, String contents) {
this.title = new Title(title);
this.contents = new Contents(contents);
}
setter 를 사용할 경우는 String 을 인자로 넘겨주어 내부적으로 값 객체를 생성하도록 했는데,
이로써 내부적인 값 객체나, 유효성 검사 로직등을 모르더라도
개발자는 게시글 엔티티를 단순히 String 값을 넘겨주면서 안전하게 개발할 수 있다!
(하지만 엔티티의 경우 jpa 를 이용하기 때문에 setter 를 최대한 사용하지 않고, 생성자나 팩토리 메서드를 이용해서 수정해주었다)
'책 > 도메인 주도 설계 철저 입문' 카테고리의 다른 글
[도메인 주도 설계 철저 입문] 6. 어플리케이션 서비스 - 도메인 서비스와의 분리! (0) | 2022.03.10 |
---|---|
[도메인 주도 설계 철저 입문] 5. 레파지토리 - 데이터와 관계된 처리를 분리하자 (0) | 2022.03.08 |
[도메인 주도 설계 철저 입문] 4. 도메인 서비스 - 부자연스러운 도메인 객체의 행동을 맡기자 (0) | 2022.03.08 |
[도메인 주도 설계 철저 입문] 3. 엔티티 - 생애주기를 갖는 객체 (0) | 2022.03.07 |
[도메인 주도 설계 철저 입문] 1. 도메인 주도 설계 읽게 된 이유 (0) | 2022.03.07 |