ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 도메인 주도 설계의 적용 - 2. 애그리게이트와 리포지토리 5부
    [옛날 글들] 도메인 주도 설계 2024. 4. 12. 21:42
    728x90

    이전글 : 도메인 주도 설계의 적용 - 2. 애그리게이트와 리포지토리 4부

    이 글은 제가 2008년 6월부터 10월까지 5개월간 마이크로소프트웨어에 연재했던 "도메인 주도 설계의 적용"이라는 원고의 원글입니다.  잡지에 맞추어 편집을 하는 과정에서 지면 상의 제약으로 인해 수정되거나 삭제된 부분이 있어 제 블로그에 원글을 올립니다. 도메인 주도 설계(Domain-Driven Design)에 관심 있는 분들에게 도움이 되었으면 합니다. 

    엔트리 포인트 리포지토리

    Order는 주문 애그리게이트 엔트리 포인트. 따라서 주문이 필요한 경우 OrderRepository를 통해 해당 Order 객체를 얻을 수 있다. 그럼 특정한 고객에 대한 주문 목록을 얻어야 한다면 어떻게 해야 할까? 쉽게 생각해 볼 수 있는 방법은 CustomerRepository로부터 Customer 객체를 얻은 후 연관을 통해 해당하는 Order 객체들에게 접근하는 것이다. 그러나 이 방법은 Order Customer 클래스 간에 양방향 연관 관계를 추가한다. 특정 객체에 속하는 다른 객체들을 조회하는 요구사항마다 양방향 연관 관계를 설정한다면 감당하기 어려울 정도로 모델이 복잡해질 것이다.

     

    고객에 해당하는 주문 목록을 조회하는 적절한 방법은 OrderRepository에 고객 별 주문 목록을 조회하는 메소드를 추가하는 것이다. OrderRepository Order의 컬렉션을 관리하기 위해 추가된 순수한 가공물(PURE FABRICATION)이다. 따라서 주문 객체를 얻기 위해 OrderRepository를 사용하는 것은 논리적으로 타당할 뿐만 아니라 주문 객체에 접근하기 위한 일관성 있는 방법을 제공한다. 또한 OrderRepository를 통해 Order Customer 간의 양방향 연관 관계를 방지할 수 있다.

     

    OrderRepository에 기능을 추가하기 전에 실패하는 테스트부터 작성하자. 테스트가OrderRepository에 대한 테스트이므로 별도의 테스트 클래스인 OrderRepositoryTest 클래스를 추가한 후 테스트 메소드를 작성하자.

    package org.eternity.customer; 
    
    import junit.framework.TestCase;
    import org.eternity.common.Registrar;
    
    public class OrderRepositoryTest extends TestCase {
      private Customer customer;   
      private OrderRepository orderRepository;
      private ProductRepository productRepository;
    
      public void setUp() throws Exception {
        Registrar.init();
        
        orderRepository = new OrderRepository();
        productRepository = new ProductRepository();
        productRepository.save(new Product("상품1", 1000));
        productRepository.save(new Product("상품2", 5000));
        
        customer = new Customer("CUST-01", "홍길동", "경기도 안양시", 200000);
      }
    
      public void testOrdreCount() throws Exception {
        orderRepository.save(customer.newOrder("CUST-01-ORDER-01")
                                     .with("상품1", 5)
                                     .with("상품2", 20)
                                     .with("상품1", 5));
    
        orderRepository.save(customer.newOrder("CUST-01-ORDER-02")
                                      .with("상품1", 20)
                                      .with("상품2", 5));
    
        assertEquals(2, orderRepository.findByCustomer(customer).size());
      }
    }

     

    두 번의 주문을 수행하여 생성된 Order 객체를 OrderRepository에 추가한다. 단언문을 사용하여 고객에게 속한 주문이 두 건인지를 검증한다. 이제 OrderRepository에 findByCustomer() 메소드를 추가하자.

    public class OrderRepository {
      public Set<Order> findByCustomer(Customer customer) {
        Set<Order> results = new HashSet<Order>();           
    
        for(Order order : findAll()) {
          if (order.isOrderedBy(customer)) {
            results.add(order);
    
          }
        }       
    
        return results;
      }
    
      @SuppressWarnings("unchecked")
      public Set<Order> findAll() {
        return new HashSet<Order>((Collection<Order>)Registrar.getAll(Order.class));
      }
    }

     

    findByCustomer() 메소드는 전체 주문 중에서 특정 고객에 속한 주문의 컬렉션을 반환한다. Order Customer 정보를 알고 있으므로 정보 전문가(INFORMATION EXPERT) 패턴에 따라 고객에 포함되어 있는 지 여부를 판단할 수 있는 isOrderedBy() 메소드를 Order에 추가한다.

    public class Order extends EntryPoint {
      public boolean isOrderedBy(Customer customer) {
        return this.customer == customer;
      }
    }

     

    Customer 클래스가 참조 객체이고 CustomerRepository에 의해 유일성과 추적성이 보장되므로 동등성 비교를 위해 ”==” 연산자를 사용했다. 테스트를 실행해 보자. 녹색 막대다. 주문도 정상적으로 처리되고 불변식도 위반하지 않고 중복되는 주문 항목도 없고 고객의 주문 목록도 조회할 수 있게 되었다. 이제 고객이 주문이 잘 못 처리됐다고 불평할 일이 없을 것 같다. 통합 빌드를 수행하고 코드를 형상 관리 툴에 체크인 한 후 어서 퇴근 준비를 서두르자

     

    2부 끝


     

    다음글 : 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 1부

    728x90
Designed by Tistory.