ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • DDD - Value Object와 Reference Object 2부
    JAVA/DDD 2020. 12. 22. 16:38

    DDD 이터니티 조용호 님의 블로그를 보고 다시 한번 공부하기 위해서 정리하게 되었습니다!

     


    불변성


    불변 클래스는 다음과 같은 규칙을 따른다.

     

    • 객체를 변경하는 메서드(mutator)를(mutator) 제공하지 않는다.
    • 재정의할 수 있는 메서드를 제공하지 않는다.
    • 모든 필드를 final로 만든다.
    • 모든 필드를 private으로 만든다.
    • 가변 객체를 참조하는 필드는 배타적으로 접근해야 한다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    public class Money {
     
      private BigDecimal amount;
     
     
     
      public Money(BigDecimal amount) {
     
        this.amount = amount;
     
      }
     
     
     
      public Money(long amount) {
     
        this(new BigDecimal(amount));
     
      }
     
     
     
      public boolean equals(Object object) {
     
        if (this == object) {
     
          return true;
     
        }
     
                
     
        if (!(object instanceof Money)) {
     
          return false;
     
        }           
     
                
     
        return amount.equals(((Money)object).amount);
     
      }
     
     
     
      public int hashCode() {
     
        return amount.hashCode();
     
      }
     
     
     
      public Money add(Money added) {
     
        this.amount = this.amount.add(added.amount);
     
        return this;
     
      }
     
     
     
      public String toString() {
     
        return amount.toString();
     
      }
     
    }
     
    cs

     

     

     

     

    • Money 클래스를 불변으로 만들기 위해 위 규칙을 적용해 보자.
    • Money 클래스를 불변으로 만들기 위해 우선 Remove Setting Method 리팩토링을 적용하자.
    • amount 필드를 private final로 변경한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Money {
     
      private final BigDecimal amount;
     
     
     
      public Money(BigDecimal amount) {
     
        this.amount = amount;
     }
     
     
            .
            .
            .
     
      }
    cs

     

     

     

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Money {
     
          private final BigDecimal amount;
     
            .
            .
     
         public Money add(Money added) {
     
          return new Money(this.amount.add(added.amount));
     
        }
        
            .
            .
            .
     
      }
    cs

     

    • 객체의 상태를 변경하는 public 메소드가 있는지 확인한다.
    • Money 클래스에는 내부 상태를 변경할 수 있는 add() 메소드가 존재한다.
    •  add() 메서드는 일반적인 setting 메서드는 아니지만 현재 진행 중인 리팩터링 문맥 상에서는 setting 메소드로 간주할 수 있다.
    • 한 가지 문제는 Money 클래스에서 add() 메서드를 제거할 경우 금액에 대한 증가 연산을 수행할 수 없다는 것이다.
    • Money 클래스의 일부로 add() 메서드를 제공하면서도 객체의 불변성을 유지할 수 있는 방법은 새로운 객체를 생성해서 반환하는 것이다.

     

     

     

     

    • 변경된 Money add() 메소드는 수신 객체의 amount와 인자로 전달된 객체의 amount의 합을  속성으로 가지는 새로운 Money 객체를 생성하여 반환한다
    • amount의 타입인 BigDecimal 역시 불변 객체이며 BigDecimal.add(BigDecimal) 메서드는 내부 상태를 변경하지 않고 새로운 BigDecimal 객체를 생성하여 반환한다
    • 따라서 Money 클래스는 금액을 증가시키는 add() 메서드를 제공하면서도 객체 자체의 불변성을 유지할 수 있게 되었다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Test
    public void testMehodAlaising() {
     
      Money money = new Money(2000);
     
      doSomethingWithMoney(money);
     
      assertEquals(new Money(2000), money);
     
    }
     
     
     
    private void doSomethingWithMoney(final Money money) {
     
      money.add(new Money(2000));
     
    }
    cs
    • 이제 다음과 같은 테스트는 성공하게 된다.
    • 별칭 문제는 객체 생성과 관련된 것이 아니라 이미 생성된 객체를 사용할 경우 발생하는 이슈이다.
    • 본문의 MoneyTest.java에서 Money를 생성하는 new Money(2000) 부분을 캡슐화한다고 해도 생성된 Money를 메서드의 인자로 넘기거나 다른 참조에 할당할 경우에는 별칭 문제가 발생하게 된다.
    • 별칭은 Reference Object(Entity)를 Value Object와 구분 짓는 특성이 된다.

     

     

    VALUE OBJECT와 불변성


    • 객체를 불변으로 만들면 별칭 문제를 피할 수 있다.
    • 객체의 상태를 바꿀 수는 없으므로 새로운 상태로 변경해야 할 경우 새로운 불변 객체를 만들어 기존의 불변 객체를 대체 시켜야 한다.
    • 객체가 불변이라면 객체를 어디에 어떤 방식으로 노출시키더라도 예상하지 못한 부작용(side effect)으로 인해 놀랄 일은 없어질 것이다.

     

    • 결론부터 이야기하자면 VALUE OBJECT는 불변 객체여야 한다.
    • VALUE OBJECT는 속성을 바꿀 수 없으며 새로운 값이 필요할 경우 기존 객체의 상태를 변경하는 대신 새로운 VALUE OBJECT를 생성해서 이를 대체해야 한다.
    • 10,000원이 들어 있는 지갑의 금액을 20,000원으로 변경하고 싶다면
    • 기존의 지갑과 연결되어 있는 Money 객체의 속성 값을 10,000에서 20,000으로 변경시키는 것이 아니라 
    • 20,000원을 속성 값으로 가지는 Money 객체를 새로 생성한 후 기존의 Money 객체 대신 새로 생성된 Money 객체를 지갑 객체와 연결시킨다
    • 이제 10,000원을 속성으로 가지고 있던 기존의 Money 객체는 가비지 컬렉션의 대상이 될 것이다.

     

     

    • VALUE OBJECT를 불변 객체로 만드는 이유는 별칭 문제와 같이 골치 아픈 문제를 피할 수 있기 때문이다.
    • VALUE OBJECT는 일반적으로 날짜금액과 같이 작은 개념을 의미하기 때문에 새로운 객체를 만들어 대체할 경우의 오버헤드가 적다
    • 추적성에도 관심을 가질 필요가 없기 때문에 굳이 동일한 객체를 계속 유지하고 있을 필요가 없다.

     

    • VALUE OBJECT는 전체 도메인의 복잡성을 낮추는 유용한 분석 개념이다.
    • 풍부한 도메인 모델(rich domain model)의 작성을 위해서는 유용하지만
    • 비즈니스 적인 관점에서 가치가 없는 작은 개념을 VALUE OBJECT로 모델링함으로써 추적성과 별칭 문제에 대한 부담 없이 해당 객체를 참조할 수 있도록 한다
    • 어떤 개념을 VALUE OBJECT로 취급하는 순간 해당 객체의 생명 주기가 얼마나 단순해질 지를 상상해 보라.

     

    • VALUE OBJECT가 반드시 불변이어야 하는 반면 REFERENCE OBJECT는 일반적으로 불변 객체가 아니다
    • 고객이나 주문과 같은 도메인 개념들은 시간에 따라 상태가 변경된다
    • 오늘 회원으로 가입한 고객의 상태가 1년 후에도 동일하게 유지될 것으로 예상하는 사람은 없을 것이다
    • 고객은 지속적으로 상품을 구매하고지불하고마일리지를 적립하며 그에 따라 고객의 상태는 계속 변경된다.

     

     

    • 시스템이 이런 이벤트에 따라 정확히 고객의 상태를 갱신하고 추적하기 위해서는 항상 동일한 고객 객체가 시스템의 각 부분으로 전달되어야 한다
    • 따라서 시스템의 모든 부분이 동일한 고객 객체를 공유해야 하며 이로 인한 REFERENCE OBJECT에 대한 별칭 문제를 피할 수는 없다
    • 정확하게 말하면 REFERENCE OBJECT에 있어 별칭은 문제가 아니라 요구사항이다
    • 시스템은 REFERENCE OBJECT의 변경 사항을 추적해야 한다.

     


    홍길동이라는 고객의 마일리지 포인트는 시스템의 어느 부분에서 참조하더라도 동일해야 한다어느 시점에 홍길동이라는 고객의 마일리지가 적립되었다면 시스템의 다른 부분에서도 변경된 마일리지 포인트를 조회할 수 있어야 한다이것이 REFERENCE OBJECT에 있어서의 추적성의 의미이다따라서 REFERENCE OBJECT에 대한 별칭은 필요악이다.

     

    • 물론 REFERENCE OBJECT를 불변 객체로 만들 수 있다면 그렇게 하는 것이 최선의 방법이다.
    • REFERENCE OBJECT를 불변 객체로 취급할 지의 여부는 요구사항에 달려 있다
    • 만약 대상이 최초 생성 시에 설정된 속성이 그대로 유지되는 추적 가능한 도메인 개념이라면 불변성을 가진 REFERENCE OBJECT로 취급하는 것이 복잡성을 낮추는 최상의 방법이다.

     

    • 가능하다면 불변 객체로 시작하라만약 객체에 대한 변경 사항이 시스템의 다른 부분으로 전파될 필요가 있다면 이를 가변 객체로 변경하라
    • 그러나 REFERENCE OBJECT의 상태를 바꾸기 위해 VALUE OBJECT의 경우처럼 새로운 REFERENCE OBJECT를 생성해서 기존 객체를 대체해서는 안 된다.
    • REFERENCE OBJECT는 시스템 내에 유일해야 한다동일한 REFERENCE OBJECT가 두 개 이상 만들어지면 시스템의 일관성이 깨진다
    • 독립적인 두 REFERENCE OBJECT에 의해 시스템의 다른 부분으로 전파될 변경 사항이 전파되지 않는 결과를 낳게 된다.
    • REFERENCE OBJECT를 불변으로 만드는 유일한 방법은 REFERENCE OBJECT의 인터페이스에 상태를 변경하는 메소드를 포함시키지 않는 것이다.

     

    정리


    • VALUE OBJECT는 객체의 상태를 변경하는 메서드를 포함할 수 있다
    • 그러나 실제로는 메시지를 수신하는 VALUE OBJECT의 상태를 변경하는 것이 아니라
    • 변경된 상태 결과를 포함하는 새로운 VALUE OBJECT를 생성하여 반환하는 것이다
    • 일반적인 REFERENCE OBJECT는 상태 변경이 가능하다
    • 만약 REFERENCE OBJECT가 불변이고 별칭 문제에 관해 신경 쓰고 싶지 않다면 객체에 상태 변경 메서드를 포함시키지 말아야 한다.
    • VALUE OBJECT는 새로운 값이 필요할 때마다 값을 가진 객체를 새로 생성한다
    • 반면 REFERENCE OBJECT는 오직 유일한 식별자를 가진 하나의 객체만이 존재해야 한다.
    • 따라서 REFERENCE OBJECT를 다룰 때는 VALUE OBJECT와 달리 오직 하나의 객체만이 생성되고 동일한 객체를 시스템의 필요 부분으로 전달하기 위한 생명 주기 제어 메커니즘이 필요하다.

     

     

     

     

    참조


    이터니티 - Domain-Driven Design의 적용

     

     

    'JAVA > DDD' 카테고리의 다른 글

    AGGREGATE와 REPOSITORY 2부  (0) 2020.12.29
    AGGREGATE와 REPOSITORY 1부  (0) 2020.12.28
    DDD - Value Object와 Reference Object 4부  (0) 2020.12.24
    DDD Value Object와 Reference Object 3부  (0) 2020.12.23
    DDD - Value Object와 Reference Object  (0) 2020.12.21
Designed by Tistory.