동현 유
척척석사
동현 유
전체 방문자
오늘
어제
  • 분류 전체보기 (181)
    • BlockChain (48)
      • [paper] Consensus (13)
      • [paper] Execution (19)
      • [paper] Storage (5)
      • [paper] ZKP (1)
      • [paper] Oracle (1)
      • Blockchains (9)
    • Java (19)
      • Java의 정석 (13)
      • Java 파헤치기 (5)
    • Python (20)
      • Python 뜯어보기 (6)
      • 데이터 분석 기초 (5)
      • Python 기초 강의 (6)
      • Python 기초 강의 부록 (3)
    • Golang (0)
    • MySQL (3)
      • programmers (2)
      • 기본 문법 (0)
    • 웹 프로젝트 (IBAS) (36)
      • Django 레거시 (14)
      • SpringBoot api 개편 (14)
      • Infra (3)
      • 서버 장애 기록 (4)
      • 신입팀원 교육 자료 (1)
    • CS (30)
      • Operating System (22)
      • Computer Security (3)
      • Network (4)
      • DBMS (1)
    • 책 (10)
      • 도메인 주도 설계 철저 입문 (9)
      • Real MySQL 8.0 (1)
    • BOJ 문제 풀이 (3)
    • 이러쿵저러쿵 (10)
    • 회고 (1)

인기 글

최근 댓글

최근 글

hELLO · Designed By 정상우.
동현 유

척척석사

[도메인 주도 설계 철저 입문] 2. "값 객체"의 개념 & 적용 예시
책/도메인 주도 설계 철저 입문

[도메인 주도 설계 철저 입문] 2. "값 객체"의 개념 & 적용 예시

2022. 3. 7. 00:50

흔히 프로그램을 작성하다보면, 사용자의 이름이나 나이 등을 원시타입(String, int) 등에 그대로 저장하는 경우가 있는데,

 

이런 경우에는 해당 값의 특성을 제대로 나타낼 수 없다.

 

이것은 여러 개발자가 함께 작업할 때 그 단점이 명확하게 나타나는데,

 

만약 String name 이라고 했을 때, 이름 값이 성과 이름을 포함하는 이름인지, 한국인 이름만 포함하는 이름인지 알 수 없기 때문에, db 를 조회해서 기존 data 가 어떤 식으로 저장되어 있는지 조회해야하는 등의 불편함이 있다.

 

따라서 원시타입을 그대로 사용하는 것이 아니라 해당 특성 값을 잘 나타낼 수 있도록 하면서,

동시에 유효성 검사도 진행할 수 있는 "값 객체"를 활용하는 것이 좋다.

 

값 객체를 도입했을 때의 장점
  1. 표현이 분명해진다.
  2. 무경성이 유지된다.
  3. 잘못된 대입을 방지한다.
  4. 유효성 검사 로직이 이곳저곳에 흩어지는 것을 방지한다.

 


게시판 생성 예시

 

 실제로 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
    동현 유
    동현 유
    Fault Tolerant System Researcher for more Trustful World and Better Lives. (LinkedIn: https://www.linkedin.com/in/donghyeon-ryu-526b8a276/)

    티스토리툴바