ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 코드를 보자.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    OrderLineItem.java
     
     
    public 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의 인터페이스에 의존하도록 하면 된다.

     

    참조


    이터니티 - Domain-Driven Design의 적용

Designed by Tistory.