ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 도메인 주도 설계의 적용 - 1. 값 객체와 참조 객체 2부
    [옛날 글들] 도메인 주도 설계 2024. 4. 10. 17:02
    728x90

    이전글 : 도메인 주도 설계의 적용 - 1. 값 객체와 참조 객체 1부

    이 글은 제가 2008년 6월부터 10월까지 5개월간 마이크로소프트웨어에 연재했던 "도메인 주도 설계의 적용"이라는 원고의 원글입니다.  잡지에 맞추어 편집을 하는 과정에서 지면 상의 제약으로 인해 수정되거나 삭제된 부분이 있어 제 블로그에 원글을 올립니다. 도메인 주도 설계(Domain-Driven Design)에 관심 있는 분들에게 도움이 되었으면 합니다. 

    불변성

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

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

    Money 클래스를 불변으로 만들기 위해 위 규칙을 적용해 보자. Money 클래스를 불변으로 만들기 위해 우선 Remove Setting Method 리팩토링을 적용하자.

     

    amount 필드를 private final로 변경한다.

    private final BigDecimal amount;

     

    객체의 상태를 변경하는 public 메소드가 있는지 확인한다. Money 클래스에는 내부 상태를 변경할 수 있는 add() 메소드가 존재한다. add() 메소드는 일반적인 setting 메소드는 아니지만 현재 진행 중인 리팩토링 문맥 상에서는 setting 메소드로 간주할 수 있다. 한 가지 문제는 Money 클래스에서 add() 메소드를 제거할 경우 금액에 대한 증가 연산을 수행할 수 없다는 것이다. 이것은 기다리던 봉급 날 아침이 밝았음에도 내 통장의 잔고가 변함없이 그대로 유지된다는 것을 의미한다.

     

    Money 클래스의 일부로 add() 메소드를 제공하면서도 객체의 불변성을 유지할 수 있는 방법은 새로운 객체를 생성해서 반환하는 것이다. , 기존 객체의 값 자체는 변경시키지 않고 연산 결과를 상태로 가지는 새로운 불변 객체를 생성하는 것이다.

    public Money add(Money added) {
      return new Money(this.amount.add(added.amount));
    }

     

    변경된 Money add() 메소드는 수신 객체의 amount와 인자로 전달된 객체의 amount의 합을  속성으로 가지는 새로운 Money 객체를 생성하여 반환한다. amount의 타입인 BigDecimal 역시 불변 객체이며 BigDecimal.add(BigDecimal) 메소드는 내부 상태를 변경하지 않고 새로운 BigDecimal 객체를 생성하여 반환한다. 따라서 Money 클래스는 금액을 증가시키는 add() 메소드를 제공하면서도 객체 자체의 불변성을 유지할 수 있게 되었다.

     

    이제 테스트를 다시 실행해 보자. 드디어 녹색 막대다. 평온과 안전의 상징인 녹색 막대가 Money 클래스의 불변성을 축하해 주고 있다. 이제 용기를 내서 좀 더 앞으로 나아가 보자.

     

    값 객체와 불변성

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

     

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

     

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

     

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

     

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

     

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

     

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

     

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

     

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

     

    정리하면 값 객체는 객체의 상태를 변경하는 메소드를 포함할 수 있다. 그러나 실제로는 메시지를 수신하는 값 객체의 상태를 변경하는 것이 아니라 변경된 상태 결과를 포함하는 새로운 값 객체를 생성하여 반환하는 것이다. 일반적인 참조 객체는 상태 변경이 가능하다. 만약 참조 객체가 불변이고 별칭 문제에 관해 신경 쓰고 싶지 않다면 객체에 상태 변경 메서드를 포함시키지 말아야 한다. 값 객체는 새로운 값이 필요할 때마다 값을 가진 객체를 새로 생성한다. 반면 참조 객체는 오직 유일한 식별자를 가진 하나의 객체만이 존재해야 한다.

     

    따라서 참조 객체를 다룰 때는 값 객체와 달리 오직 하나의 객체만이 생성되고 동일한 객체를 시스템의 필요 부분으로 전달하기 위한 생명 주기 제어 메커니즘이 필요하다.

     

    다음글 : 도메인 주도 설계의 적용 - 1. 값 객체와 참조 객체 3부

    728x90
Designed by Tistory.