-
DDD - Dependency Injection과 AOP 3부JAVA/DDD 2021. 1. 22. 22:03
영속성(Persistence)과 REPOSITORY
- REPOSITORY는 도메인 객체 생성 이후의 생명주기를 책임진다.
- 도메인 객체가 생성되고 상태가 초기화된 후에는 REPOSITORY에게 넘겨진다.
- REPOSITORY는 객체를 넘겨받아 내부 저장소에 보관하고 요청이 있을 경우 객체를 조회하여 반환하거나 삭제한다.
- 클라이언트 입장에서 REPOSITORY는 커다란 메모리에 도메인 객체들을 담고 있는 객체 풀(object pool)과 같다.
- 클라이언트는 생성된 객체를 REPOSITORY에게 전달하고 객체가 필요한 경우 REPOSITORY에게 객체 풀 안의 어딘가에 잠자고 있는 도메인 객체를 찾아 달라고 요청한다.
- REPOSITORY의 기능을 메모리 컬렉션에 대한 오퍼레이션으로 바라보는 것은 도메인 모델을 단순화하기 위한 중요한 추상화 기법이다.
- 도메인 모델을 설계하고 필요한 오퍼레이션을 식별하는 동안 우리는 하부의 어떤 메커니즘이 도메인 객체들의 생명 주기를 관리하는지에 관한 세부사항을 무시할 수 있다.
- REPOSITORY가 제공하는 인터페이스의 의미론을 메모리 컬렉션에 대한 관리 개념으로 추상화함으로써 자연스럽게 하부의 데이터 소스와 관련된 영속성 메커니즘을 도메인 모델로부터 분리할 수 있다.
- 복잡성을 관리하는 중요한 방법은 서로 다른 관심사를 고립시켜 한 번에 하나의 이슈만을 해결하도록 하는 것이다.
- 도메인을 모델링 할 때는 REPOSITORY를 통해 모든 객체가 메모리에 있다는 착각을 줌으로써 하부 인프라 스트럭처에 대한 부담 없이 도메인 로직에 집중할 수 있다.
- 또한 하부의 데이터 접근 로직을 REPOSITORY에 집중시킴으로써 도메인 로직과 데이터 접근 로직을 자연스럽게 분리시킬 수 있다.
- 영속성 메커니즘이 REPOSITORY 내부로 제한되어 있기 때문에 도메인 모델에 영향을 미치지 않고서도 영속성 메커니즘을 교체하는 것이 가능하다.
- 따라서 REPOSITORY를 모델링 할 때는 하부의 영속성 메커니즘에 관한 세부사항을 배제하고 메모리 컬렉션을 관리하는 객체로 모델링한다.
- REPOSITORY의 인터페이스는 메모리 내의 객체 풀을 관리한다는 의도를 나타내도록 명명한다.
- REPOSITORY를 사용하는 클라이언트 역시 데이터베이스에 대한 고려는 하지 않는다.
- REPOSITORY의 클라이언트는 객체 정보가 일반 파일에 저장되어 있는지, XML에 저장되어 있는지, 데이터베이스에 저장되어 있는지 관심조차 없다.
- REPOSITORY를 구현할 때는 잠시 관점을 바꾸어 현재 사용중인 하부의 데이터 소스를 고려해야 한다.
- 지금까지 객체 지향 신봉자로 살아 왔다면 지금부터는 데이터베이스 신봉자로 살아야 한다.
- REPOSITORY를 사용하는 클라이언트를 개발할 때는 하부의 데이터 소스를 몰라도 되지만 내부 구현을 할 때는 그렇지 않다.
Eric Evans는 그의 저서 Domain-Driven Design에서 REPOSITORY를 다음과 같이 설명하고 있다.
전역적으로 접근 될 필요가 있는 각 객체 타입에 대해 해당 타입의 모든 객체들을 메모리 컬렉션으로 저장하고 있는 듯한 착각을 일으키는 객체를 생성한다.
잘 알려진 전역 인터페이스를 통해 이 객체들에 접근할 수 있도록 한다. 실제 데이터 저장소에 데이터를 추가하고 삭제하는 실제적인 작업을 캡슐화하는 추가/삭제 메소드를 작성한다.
특정 쿼리 조건을 만족하는 객체 또는 객체들의 컬렉션을 반환하는 조회 메소드를 추가함으로써 실제 저장소와 쿼리 기술을 캡슐화한다. 모든 객체 저장소와 접근을 REPOSITORY로 위임함으로써 클라이언트가 모델에만 초점을 맞추도록 한다.- 결론적으로 REPOSITORY는 영속성 메커니즘을 캡슐화하기 위한 훌륭한 지점이다
- . 따라서 우리가 개발한 주문 어플리케이션에 영속성 메커니즘을 추가하기 위해서는 REPOSITORY의 내부 구현을 바꾸어야 한다.
- 그러나 이를 위해서는 한 가지 풀어야 할 숙제가 남아 있다. 바로 도메인 객체와 REPOSITORY의 결합도를 낮추는 것이다.
OrderLineItem의 코드를 보자.
123456789101112OrderLineItem.javapublic class OrderLineItem {private ProductRepository productRepository = new ProductRepository();public OrderLineItem(String productName, int quantity) {this.product = productRepository.find(productName);this.quantity = quantity;}}cs - OrderLineItem은 인스턴스 변수로 ProductRepository를 포함하며 클래스 로딩 시 new연산자를 사용하여 ProductRepository의 인스턴스를 직접 생성한다.
- OrderLineItem은 생성자에 전달된 상품명을 가진 Product 객체를 찾기 위해 ProductRepository를 사용한다.
- 이제 REFERENCE OBJECT들을 메모리가 아닌 관계형 데이터베이스 내에 관리하기로 정책을 수정했다고 하자.
- 기존의 ProductRepository는 메모리 컬렉션을 관리하는 Registrar를 사용하고 있다.
- 따라서 ProductRepository의 내부 코드를 수정할 수밖에 없다. 이것은 OCP(OPEN-CLOSE PRINCIPLE) 위반이다.
- 하지만 이제부터는 REFERENCE OBJECT들을 데이터베이스에서만 관리하면 된다.
- REFERENCE OBJECT를 메모리에서 관리할 필요가 없다면 ProductRepository 내부를 변경한다고 해서 문제 될 것은 없다.
- 우리는 이미 REFERENCE OBJECT에 대한 처리 로직을 REPOSITORY 내부로 고립시켰다.
- 현재의 설계가 변경에 따른 파급 효과를 최소화할 수 있는 구조를 가지고 있기 때문에 REPOSITORY 내부를 수정하는 것이 그렇게 큰 문제가 될 것 같지는 않다.
- 어차피 시간도 없는데 OCP까지 고려할 필요가 있을까? 눈 딱 감고 ProductRepository를 수정해서 데이터베이스 접근 코드를 추가해도 되지 않을까?
- 그러나 문제는 엉뚱한 곳에서 발생한다. OrderLineItem은 ProductRepository에 의존한다.
- ProductRepository는 데이터베이스에 의존한다. 따라서 OrderLineItem 역시 데이터베이스에 의존하고 OrderLineItem을 사용하는 모든 Customer, Order 역시 데이터베이스에 의존한다.
- 따라서 OrderLineItem을 사용하는 모든 도메인 클래스들이 데이터베이스에 직간접적으로 의존하는 결과를 낳는다.
- 즉, 거의 대부분의 도메인 클래스가 데이터베이스와 결합되어 있다.
- 따라서 단위 테스트를 수행하기 위해서는 DBMS가 실행 중이어야 하고 필요한 데이터들이 미리 입력되어 있어야 하며 각 단위 테스트가 종료된 후에는 다른 테스트에 영향을 미치지 않도록 모든 데이터베이스의 상태를 초기화해야 한다.
- 데이터베이스는 속도가 느리고 따라서 결과에 대한 피드백 또한 느리다.
- 만사가 그렇겠지만 불편하다면 피하게 될 확률이 높다. 단위 테스트가 개발의 리듬을 방해해서는 안 된다.
- 따라서 단위 테스트를 데이터베이스로부터 독립시켜야 한다.
- 문제는 결합도(coupling)이다. OrderLineItem은 ProductRepository에 강하게 결합되어 있다.
- 두 클래스가 강하게 결합되어 있으므로 ProductRepository 없이 OrderLineItem이 존재할 수 없다.
- 따라서 OrderLineItem을 사용하려면 ProductRepository가 존재해야 하고, ProductRepository가 존재하기 위해서는 데이터베이스가 구동 중이어야 한다.
- 결합도는 양날의 검이다. 객체 간의 결합은 자연스러운 것이다.
- 각 클래스가 높은 응집도를 유지하기 위해 다른 클래스와 협력하는 것은 객체 지향의 기본 원리이다.
- OrderLineItem은 Product REFERENCE OBJECT를 얻기 위해 ProductRepository와 협력해야 한다.
- 문제는 OrderLineItem과 ProductRepository 간의 결합도가 필요 이상으로 높다는 것이다.
- OrderLineItem이 직접 ProductRepository의 인스턴스를 생성하기 때문에 OrderLineItem과 ProductRepository를 서로 분리시킬 수 있는 방법이 없다.
- 즉, 구체적인 클래스인 OrderLineItem이 또 다른 구체적인 클래스인 ProductRepository에 의존한다는 것이 문제가 되는 것이다.
- 대부분의 구체적인 클래스 간의 의존성은 전체적인 어플리케이션의 유연성을 저해한다.
- 결합도를 낮추는 일반적인 방법은 OrderLineItem과 ProductRepository 간의 직접적인 의존 관계를 제거하고 두 클래스가 추상에 의존하도록 설계를 수정하는 것이다.
- 즉, 구체적인 클래스가 추상적인 클래스에 의존하게 함으로써 전체적인 결합도를 낮추는 것이다.
- 구체적인 클래스들 간의 의존 관계를 추상 계층을 통해 분리함으로써 OCP를 위반하는 설계를 제거할 수 있다.
- 따라서 OrderLineItem과 ProductRepository가 모두 인터페이스에 의존하도록 설계를 수정한다면 유연하고 낮은 결합도를 유지하면서도 OCP를 위반하지 않는 설계를 만들 수 있다.
- 두 클래스가 인터페이스에 의존하도록 수정하는 가장 간단한 방법은 무엇일까?
- ProductRepository를 인터페이스와 구현 클래스로 분리하고 OrderLineItem과 ProductRepository의 구현 클래스가 ProductRepository의 인터페이스에 의존하도록 하면 된다.
참조
'JAVA > DDD' 카테고리의 다른 글
DDD - Dependency Injection과 AOP 6부 7부 (0) 2021.01.23 DDD - Dependency Injection과 AOP 4부 5부 (0) 2021.01.22 DDD - Dependency Injection과 AOP 2부 (0) 2021.01.22 DDD - Dependency Injection과 AOP 1부 (0) 2021.01.11 AGGREGATE와 REPOSITORY 5부 (0) 2021.01.08