Chapter Six. The Life Cycle of a Domain Object
모든 객체는 생명 주기(life cycle)를 가지고 있다. 다양한 상태를 거쳐서 객체는 태어나고, 일정기간 동안 보관되거나(archived) 삭제되면서 점차적으로 사라진다.
문제는 2가지 범주로 나뉜다.
1. 수명 주기(life cycle) 동안 무결성(integrity) 유지.
2. 수명 주기 관리의 복잡성으로 인해 모델이 수렁에 빠지는 것을 방지.
3가지 패턴을 통해서 이러한 이슈를 다룰 것이다.
첫째, AGGREGATES는 명확한 소유권과 경계를 정의하고, 객체가 혼란스럽고 그물처럼 얽힌 것을 피하면서 모델 자체를 강화한다.
이 패턴은 수명 주기 동안 모든 단계에서 무결성을 유지하는데 중요하다.
둘째, 복잡한 객체와 내부 구조 캡슐화를 유지하는 AGGREGATES를 생성하고 재구성하기 위해 FACTORIES를 사용하여 수명 주기 초기로 돌린다.
셋째, REPOSITORIES는 뒤얽힌 광대한 인프라스트럭쳐를 캡슐화하여 지속적인 객체(persistent object)를 찾고 검색하는 수단을 제공하고, 수명 주기의 중간 단계와 끝 부분을 처리한다.
비록, REPOSITORIES and FACTORIES는 도메인에서 나온 것은 아니지만, 도메인 디자인에서 의미있는 역할을 한다.
AGGREGATES는 수명 주기의 모든 단계에서 불변을 유지해야 하는 범위를 표시한다.
FACTORIES and REPOSITORIES는 특정 수명 주기의 복잡성을 캡슐화하여 AGGREGATES에 작용한다.
Aggregates
(집합)
하나의 독립된 객체에 대해 수정/삭제하고자 하는 요청이 동시에 발생했을 때, 순서를 보장해야 한다.
복잡한 연관성을 가진 모델에서 객체를 변경하는 것에 대한 일관성을 보장하는 것은 어렵다.
개별 객체 뿐만 아니라, 밀접하게 관련된 객체 그룹에 적용되는 불변성을 유지 관리해야 한다.
그러나 조심스런 잠금 계획(locking scheme)은 여러 사용자들이 서로를 무의미하게 간섭하게 하고 시스템을 사용할 수 없게 한다.
우선, 모델 내에서 참조를 캡슐화하기 위한 추상화가 필요하다.
AGGREGATE는 데이터 변경 목적을 위해 하나의 단위로 취급되는 연관된 객체의 클러스터이다.
각각의 AGGREGATE는 Root와 Boundary(경계)를 가지고 있다.
Boundary는 AGGREGATE안에 있는 것을 정의한다.
Root는 AGGREGATE에 포함되어 있는 특별한 단일 ENTITY이다.
Root는 단지 외부 객체가 참조할 수 있는 AGGRETATE의 구성원일 뿐이지만, Boundary내에 있는 객체들은 서로를 참조할 수 있다.
ENTITIES는 Root가 아닌 로컬 ID를 가지지만, 외부 객체가 Root ENTITY 컨텍스트 밖에서는 볼 수 없기 때문에 해당 ID를 AGGREGATE내에서 구별할 수 있어야 한다.
데이터가 변경될 때마다 유지되어야 하는 일관성 규칙(consistency rule)인 불변성(invariant)은 AGGREGATE의 구성원 사이의 관계를 포함한다. AGGREGATES를 채우는 모든 규칙은 항상 최신 상태로 유지되는 것은 아니다.
Event Processing, Batch Processing, 또는 다른 업데이터 메커니즘을 통해서 특정 시간 내에 다른 종속성을 해결할 수 있다.
그러나 AGGREGATE내 적용된 불변성은 각 트랜잭션의 완료와 함께 시행될 것이다.
ENTITIES와 VALUE OBJECTS를 AGGREGATES로 클러스터하고 각각의 경계를 정의하라(Define Boundary around each). 하나의 Entity를 선택하여 각 Aggregate의 Root가 되게 하고, Root를 통해서 경계 내부 객체에 대한 모든 접근을 통제하라.
외부 객체는 단지 Root만을 참조할 수 있도록 하라.
내부 구성원에 대한 일시적인 참조는 단일 동작 내에서만 사용할 수 있다.
Root가 접근에 대해서 통제를 하기 때문에, 내부를 변경하여 공격할 수 없다.
이러한 배열은 Aggregate에 있는 객체에 대한 모든 불변성과 모든 상태 변화에 따른 Aggregate의 불변성을 적용하는 것을 실용적으로 만든다.
Factories
객체나 전체 Aggregate를 만들 때, 내부 구조가 너무 복잡해지거나 너무 많이 드러날 경우, FACTORIES가 캡슐화를 제공한다.
객체의 생성은 그 자체로 주요한 작업이 될 수 있지만, 복잡한 조립(assembly) 작업은 생성된 객체의 책임으로는 적합하지 않다.
그러한 책임을 결합하는 것은 이해하기 어려운 꼴사나운 설계를 만들 수 있다.
When a Constructor Is All You Need
다음 환경에서 public constructor를 선호한다.
- 클래스는 타입이다. 클래스는 흥미로운 계층 구조의 일부가 아니고, 인터페이스 구현으로 여러 가지 형태(다형성)로 사용되지 않는다.
- 클라이언트는 STRATEGY를 선택하는 방법으로 구현을 고려한다.
- 객체의 모든 속성을 클라이언트에서 사용할 수 있기 때문에 클라이언트에 노출된 생성자 내부에 객체 생성이 중첩(nested)되지 않는다.
- 생성은 복잡하지 않다.
- public 생성자는 FACTORY와 동일한 규칙을 따라야 한다. 생성된 객체의 모든 불변성을 만족시키는 원자 연산(automic operation)이어야 한다.
다른 클래스의 생성자 내에서 생성자를 호출하는 것을 피하라.
생성자는 정말로 간단해야 한다.
복잡한 구성, 특히 Aggregate가 그렇게 복잡할 때, Factory를 요구한다.
Designing the Interface
독립형이든 FACTORY METHOD를 사용하든 상관없이 FACTORY의 method 서명을 설계할 때는 2가지 사항을 염두에 두어야 한다.
- 각 동작은 원자적이여야 한다. (must be automic) FACTORY와 단일 상호작용으로 완전한 생산품을 만드는데 필요한 모든 것을 전달해야 한다. 또한, 일부 불변성이 만족스럽지 않은 경우, 생성이 실패했을 때 발생할 수 있는 것이 무엇인지 결정해야 한다. Exception이나 Null을 던질 수 있다. 일관성을 유지하기 위해서, FACTORIES의 실패에 대한 코딩 표준을 채택하는 것을 고려하라.
- FACTORY는 인자(arguments)에 연결될 것이다. 입력 변수를 신중하게 선택하지 않는다면, 난잡한 의존성을 만들 수 있다. 연결성 정도는 인자로 무엇을 하는지에 달려 있다. 생산품에 간단히 연결된다면, 적당한 의존성을 가질 수 있지만, 만약 생성에 사용하기 위해서 인자 중에서 일부를 고른다면, 연결 정도는 더욱 단단해진다.
Repositories
연관성(associations)을 통해서 다른 것과의 관계를 기반으로 객체를 찾을 수 있다. 그러나 생명 주기(life cycle) 중간에 ENTITY나 VALUE를 가로지르는 출발점이 있어야 한다.
객체를 통해서 무엇인지를 하기 위해서는 객체에 대한 참조를 가지고 있어야 한다.
그 참조를 어떻게 얻을 수 있는가? 한가지 방법은 생성 작업이 새로운 객체에 대한 참조를 반환하기 때문에 객체를 생성하는 것이다.
두번째는 연결성(association)을 횡단(traverse)하는 것이다. 이미 알고 있는 한가지 객체에서 시작하여 연관된 객체를 요구한다.
세번째 방법은 관계형 데이터베이스(RDBMS)를 이용하는 것이다. 쿼리를 통해서 데이터베이스에 있는 속성 기반으로 객체를 찾을 수 있다.
클라이언트는 기존 도메인 객체에 대한 참조를 얻는 실제적인 수단이 필요하다. 만약 인프라스트럭처를 통해서 쉽게 구현할 수 있다면, 클라이언트 개발자는 모델을 진흙탕으로 만들면서 더 많은 연관성(association)을 추가할 것이다. 다른 한편으로는 데이터베이스에서 필요한 정확한 데이터를 가져오기 위해서 쿼리를 사용하거나, Aggregate Root를 탐색하기보다는 일부 특정 개체를 가져오기 위해서 쿼리를 사용할 수 있다. 도메인 로직은 쿼리와 클라이언트 코드로 이동하고, ENTITIES와 VALUE OBJECTS는 단지 데이터 컨테이너가 된다. 대부분의 데이터베이스가 인프라스트럭처에 접근하기 위해 적용하는 기술적인 복잡성으로 인해 클라이언트 코드가 빠르게 변하기 때문에, 개발자들은 도메인 레이어를 바보 취급하고 모델과는 아무런 상관이 없게 된다.
영속하는 객체(Persistent Object)는 객체의 속성에 기반한 검색을 통해서 전역으로 접근가능해야 한다.
그러한 접근은 횡단을 통해서 도달하기 쉽지 않은 Aggregate의 Root에 필요하다.
일반적으로는 ENTITIES이고, 때때로 복잡한 내부 구조를 가지고 있는 VALUE OBJECTS이고, 가끔은 열거된 값을 가지는 VALUES이다.
다른 객체에 대한 접근을 제공하면, 중요한 차이점이 없어진다.
자유로운 데이터베이스 쿼리는 실제로 도메인 객체와 Aggregate 캡슐화를 위반할 수 있다.
기술적인 인프라스트럭처와 데이터베이스 접근 메커니즘에 대한 노출은 클라이언트를 복잡하게 만들고, MODEL-DRIVEN DESIGN을 모호하게 한다.
REPOSITORY는 개념적인 집합(보통 모방하여)으로 특정 타입에 대한 모든 객체를 표현한다. REPOSITORY는 더 정교한 쿼리 능력을 제외한 collection처럼 동작한다.
적절한 타입의 객체가 추가되거나 삭제되면, REPOSITORY 뒤에 있는 기계는 데이터베이스에 객체를 삽입하거나 삭제한다.
클라이언트는 일반적으로 특정 속성값인 클라이언트에 의해 지정된 규격에 기반한 객체를 선택하는 쿼리 메소드(query method)를 사용하여 REPOSITORY에 객체를 요청한다. REPOSITORY는 데이터베이스 쿼리와 메타데이터 맵핑에 대해 캡슐화된 기계작업에 의해 요청된 객체를 조회한다.
REPOSITORY는 클라이언트가 요청한 규격이 무엇이건 간에 그 규격에 기반한 객체를 선택하는 다양한 쿼리를 구현할 수 있다.
또한, 해당 규격을 만족시키는 인스턴스의 숫자와 같은 요약 정보도 제공할 수 있다. 특정 숫자 속성에 대해서 일치하는 객체의 전체 숫자와 같은 요약 계산 정보도 제공할 수 있다.
글로벌 액세스가 필요한 객체의 각 타입에 대해서 해당 모든 객체의 in-memory collection에 대한 환상을 제공할 수 있는 객체를 생성하라.
잘 알려진 글로벌 인스턴스를 통해서 액세스를 제공하라. 데이터 저장소에 실제로 데이터를 입력하거나 삭제하는 기능을 캡슐화한 객체를 추가/삭제할 수 있는 method를 제공하라. 어떤 규격에 따라 객체를 선택하는 method를 제공하고, 전체적으로 인스턴스화한 객체나 규격에 만족하는 속성 값을 가진 객체의 collection을 제공하는 method를 제공하여 실제 스토리지와 쿼리를 캡슐화하라. 직접적인 액세스가 필요한 Aggregate root에 대해서만 Repository를 제공하라. 모델에 기반한 클라이언트를 유지하고, Repository에 접근하도록 모든 객체 스토리지를 위임하라.
REPOSITORY 개념은 많은 상황에서 채택 가능하다. 구현 가능성은 다양하지만, 염두에 두어야 하는 몇 가지 항목은 다음과 같다.
- 타입을 추상화하라. REPOSITORY는 특정 타입의 모든 인스턴스를 "포함한다(contains)". 그러나, 각 클래스마다 하나의 REPOSITORY가 필요하다는 것을 의미하는 것은 아니다. 데이터베이스 기술의 다형성(polymorphism)에 대한 부족으로 인해 나타난 제약 조건들에 직면할 수 있다는 것을 염두에 두어야 한다.
- 클라이언트와의 분리에 대해 장점을 취하라. 클라이언트에서 직접적으로 호출하는 것에 비해서 REPOSITORY 구현을 더 자유롭게 변경할 수 있다. 다양한 쿼리 기술과 메모리에 객체를 캐싱하는 것, 영속 데이터 저장 전략을 언제라도 자유롭게 변경하여 성능에 대한 최적화를 할 수 있다.
- 클라이언트에 트랜잭션 컨트롤을 남겨 두라. 비록 REPOSITORY가 데이터베이스에 입력하고 삭제할지라도, 일반적으로는 어떤 것도 커밋(commit)하지 않는다. 클라이언트에서 컨텍스트를 완벽하게 초기화하고 작업 단위를 커밋한다.
The Relationship with FACTORIES
FACTORY는 객체의 생애(life)에 대한 시작(beginning)을 다룬다. REPOSITORY는 객체 생애의 중간과 마지막을 관리한다. 객체가 메모리에 있거나 Object Database에 저장되어 있을 때는 간단하다. 그러나 일반적으로 적어도 일부 객체는 RDB, File, Non object-oriented system에 저장된다. 그러한 경우에 데이터 탐색은 객체 형태로 재구성되어야 한다.
이 경우에 REPOSITORY는 데이터에 기반하여 객체를 생성하기 때문에 많은 사람들은, 사실 기술적인 관점에서, REPOSITORY를 FACTORY로 고려한다.
Domain-Driven Design관점에서 FACTORY와 REPOSITORY는 책임이 구별되어 있다. FACTORY는 새로운 객체를 생성하고, REPOSITORY는 이전의 객체를 찾는다.
댓글을 달아 주세요
댓글 RSS 주소 : http://www.yongbi.net/rss/comment/825