Eternity's Chit-Chat

aeternum.egloos.com



Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 3부 Domain-Driven Design

영속성(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 코드를 보자.


OrderLineItem.java
public class OrderLineItem { 

private ProductRepository productRepository = new ProductRepository();

      

public OrderLineItem(String productName, int quantity) {

this.product = productRepository.find(productName);

       this.quantity = quantity;

}


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 인터페이스에 의존하도록 하면 된다.


핑백

  • Domain-Driven Design | Jongmin Kim's Blog 2014-09-02 01:18:25 #

    ... ion과 Aspect-Oriented Programming 4부 2008/12/13 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 3부 2008/12/09 Domain-Driven Design의 적용-3.Depe ... more