06 - 도메인 객체의 생명주기
Last updated
Last updated
모든 객체에는 생명주기가 있다. 한 객체는 생성되어 다양한 상태를 거친 후 결국 저장되거나 삭제되면서 소멸한다.
물론 이들 중 상당수는 객체에 생성자를 호출해서 만들어진 다음 특정 연산에서 사용된 후 가비지 컬렉터에게 보내지는 단순하고 일시적인 객체이다. 이런 객체는 복잡하게 만들 필요가 없다.
그러나 일시적이지 않은 객체들은 더 오래 지속되며, 다른 객체와 복잡한 상호작용을 맺는다.
이러한 객체들을 잘 관리하는 것이 DDD 에서 중요한 점이다. (도메인을 잘 이해해야 각각의 객체를 잘 구분할 수 있다)
생명주기 동안의 무결성 유지하기
생명주기 관리의 복잡성으로 모델이 난해해지는 것을 방지하기
이를 위한 해결책으로 AGGREGATE, FACTORY, REPOSITORY 가 있다.
객체들이 밀접한 연관관계를 맺는 객체 집합에는 불변식이 적용되어야한다. (일관성 있는 객체 상태를 유지하기 위해서)
불변식 : 데이터가 변경될 때마다 유지되어야 하는 규칙
모델 내에 참조에 대한 캡슐호하의 추상화가 필요하고, 그것이 바로 Aggreate(데이터 변경의 단위로 다루는 연관 객체의 묶음)이다.
루트(Root) 와 경계(Boundary) 가 있다.
루트 Entity는 전역 식별성을 지니며, 궁극적으로 불변식을 검사할 책임이 있다.
각 루트 Rntity 는 전역 식별성을 지닌다. 경계 안의 Entity 는 지역 식별성을 지니며, 이러한 지역 식별성은 해당 Aggregate 안에서만 유일하다.
Aggreate 경계 밖에서는 루트 Entity 를 제외한 Aggreate 내부의 구성요소를 참조할 수 없다.
데이터베이스 Query 를 이요하면 Aggreate 의 루트만 직접적으로 획득할 수 있다.
Aggregate 안의 객체는 자신이 속한 루트만 참조할 수 있다.
삭제 연산은 Aggregate 경계 안의 모든 요소를 한 번에 제거해야 한다.
Aggregate 경계 안의 어떤 객체를 변경하더라도 전체 Aggregate 의 불변식은 모두 지켜져야 한다.
Entity 와 Value Object 를 Aggregate 로 모으고 각각에 대해 경계를 정의하라.
한 Entity 를 골라 Aggregate Root 로 만들고, Aggregate 경계 내부에 객체에 대해서는 루트를 거쳐 접근할 수 있도록 하라.
Aggregate 밖의 객체는 루트만 참조할 수 있도록 하라.
내부 구성요소에 대한 일시적인 참조는 단일 연산에서만 사용할 목적에 한해 외부로 전달될 수 있다.
루트를 경유하지 않고 Aggregate 의 내부를 변경할 수 없다.
이런식으로 Aggregate 의 각 요소를 배치하면 Aggregate 안의 객체와 전체로서의 Aggregate 의 상태를 변경할 때 모든 불변식을 효괒정으로 이행할 수 있다. (데이터 무결성을 지키는 좋은 방법)
어떤 객체나 전체 Aggregate 를 생성하는 일이 복잡해지거나 내부 구조를 너무 많이 드러내는 경우 Factory 가 캡슐화를 제공해준다.
어떤 객체를 생성하는 것이 그 자체로도 주요한 연산이 될 수 있지만, 복잡한 조립 연산은 생성된 객체의 책임으로 어울리지 않는다. 이런 책임을 클라이언트에 두면 이해하기 힘든 볼품없는 설계로 이어질 수 있다. (또한 조립 연산은 클라이언트의 책임이라 보기 어렵다..)
우리는 자동차를 운전할 때 어떻게 조립 되었느냐는 신경쓰지 않는다. 두 가지 행위는 같은 시간에 일어나지 않으며, 이런 기능이 동일한 매커니즘에 결합되어 있는 것은 의미가 없기 때문이다.
복잡한 객체와 Aggregate 인스턴스를 생성하는 책임을 별도의 객체로 옮겨라. (Factory)
복합한 객체를 생성한다는 것은 도메인의 책임이기는 하나 그것이 업무의 모델을 표현하는 것은 아니다.
일반적으로 객체의 생성과 조립은 도메인에서 의미가 없다. 하지만 구현 측면에서 반드시 필요하다.
Factory 를 만들기로 했다면 제어할 수 있는 어딘가에 위치시켜야 한다. 위치를 결정할때는 보통 Aggregate 를 중심으로 판단한다.
Factory 생성이 가능한 위치는 다음과 같다 .
Aggregate Root : 정적 메서드를 통한 Factory
Domain Service : Service 기반 Factory
...
이미 존재하는 Aggregate 에 특정 요소를 추가하는 경우를 생각해보자.
위 다이어그램은 기존에 존재하는 Aggregate PO 에 Line item 요소를 추가하는 과정을 나타낸것이다. Client 는 새로운 item 에 대한 PartID 와 수량만 넘겨주면 Aggregate 는 Line Item 을 추가하고 무결성을 보장하기 위해서 불변식 실행을 담당한다.
Factory Method 가 반드시 같은 Aggregate 내의 객체 생성만 담당하는 것은 아니다. 객체간의 밀접한 관련이 있는 경우 다른 Aggregate 의 객체 생성을 담당하기도 한다.
객체를 생성할 때 Factory 를 사용하면 많은 이점이 있지만, 반드시 그래야하는 것은 아니다. 때로는 간단한 생성자를 사용하는 것이 더 좋을때도 있다. 만약 아래와 같은 상황이라면 공개 생성자를 사용하는 방식을 고려해보자.
클래스가 곧 타입인 경우 : 어떤 계층구조도 가지고 있지 않으며, 인터페이스를 구현하는 다형성으로 사용되지 않는 경우
클라이언트가 전략을 선택하는 방법으로 구현체에 관심 있는 경우
생성자가 복잡하지 않은 경우
Factory 는 자신의 생성물과 특별한 관계를 맺는다. 해당 생성물에 대해 내부 구현을 알고 있으며 밀접한 관계를 갖기 떄문이다.
이런 경우 불변식이 생성자가 아닌 Factory 에 위치 시킨다면 생성물의 복잡도를 줄이는데 도움이 된다.
단 주의할 점은 위와 같은 경우 Aggregate 규칙에는 잘 맞지만 다른 도메인 객체에 속한 객체를 생성하는 Factory Method 에 경우에는 적용되지 않는다.
기존에는 DB 와 객체간의 패러다임 불일치 해결을 위한 기술적인 이슈 떄문에 많은 에너지를 소모하게 되었고, 그로 인해 도메인 자체의 집중력이 떨어지고 도메인에 관한 내용도 객체 DB 매핑 전에 들어가기 시작했다.
그러한 이슈를 해결하기 위해서 인프라스트럭처에 접근하는 행위도 캡슐화하고 DB 에 의존적이지 않고 순수한 객체로 핸들링할 수 있는 수단이 필요했다. 그것이 Repository 가 해결해준다.
객체 재구성의 책임 (DB, OOP 간의 패러다임 불일치 해결)
영속화 객체 접근의 캡슐화
클라이언트는 Repository 를 사용하므로써 각 객체 타입에 대해 메모리상에 해당 타입의 객체로 구성된 컬랙션이 있다고 착각을 불러 일으키는 객체를 만든다.
Aggregate 루트에 대해서만 Repository 를 제공하고, 모든 객체 저장과 접근은 Repository 에 위임해서 클라이언트가 모델에 집중하게 한다.
Repository 는 영속화된 객체를 획득하고, 해당 객체의 생명주기를 관리하기 위해 단순한 모델을 클라이언트에 제시한다.
Repository 는 영속화 기술과 다수의 데이터베이스 전략, 또는 심지어 다수의 데이터 소스로부터 애플리에이션과 도메인 설계를 분리한다.
Repository 는 객체 접근에 대한 메서드를 제공해준다.
Repository 를 이용하면 테스트에서 사용할 Mock 객체를 손쉽게 만들 수 있다.
직접 구현은 쉽지 않다.
최근에는 Repository 를 구현하는 ORM 기술과 같은 것들을 사용할 수 있다 .
팩토리 메서드 패턴