Chapter 02. 아키텍처 개요

beanii·2023년 3월 13일
0

DDD Study

목록 보기
2/11
post-thumbnail

2.1 네 개의 영역

1. 표현 영역

  • 사용자의 요청을 받아 응용 영역에 전달하고 응용 영역의 처리 결과를 다시 사용자에게 보여주는 역할
    ex) 스프링 MVC 프레임워크
  • 표현 영역이 하는 일
    • 웹 브라우저가 전송한 HTTP 요청 파라미터를 응용 서비스가 요구하는 형식의 객체 타입으로 변환해서 전달
    • 응용 서비스가 리턴한 결과를 JSON 형식으로 변환해서 HTTP 응답으로 웹 브라우저에 전송

2. 응용 영역

  • 시스템이 사용자에게 제공해야 할 기능을 구현
  • 도메인 영역의 도메인 모델 사용
  • 로직을 직접 수행하기보다는 도메인 모델에 로직 수행을 위임
public class CancelOrderService {
	@Transactional
    public void cancelOrder(String orderId) {
    	Order order = findOrderById(orderId);
        if(order == null) 
        	throw new OrderNotFoundException(orderId);
        order.cancel();
    }
    ...
}

3. 도메인 영역

  • 도메인이 핵심 로직을 구현하는 도메인 모델 구현
    ex) Order, OrderLine, ShippingInfo와 같은 도메인 모델 구현, 여기서 '배송지 변경', '결제 완료'와 같은 핵심 로직 구현

4. 인프라스트럭처 영역

  • 논리적인 개념을 표현하기보다는 구현 기술에 대한 것을 다룸
    • RDBMS 연동을 처리
    • 메시징 큐와의 메시지를 전송 수신 기능 구현
    • 몽고DB, 레디스와의 데이터 연동
    • SMTP를 이용한 메일 발송 기능 구현
    • HTTP클라이언트 이용해서 REST API 호출

  • 도메인 영역, 응용 영역, 표현 영역에서는 구현 기술을 사용한 코드를 직접 만들지 않고, 인프라스트럭처 영역에서 제공하는 기능을 사용해서 필요한 기능 개발
    ex) 응용 영역에서 DB에 보관된 데이터 읽어오기 위해 인프라스트럭처에 영역의 DB 모듈을 사용

2.2 계층 구조 아키텍처

  • 계층 구조의 아키텍처 구성

  • 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않음

  • 구현의 편리함을 위해 상위 계층이 바로 아래 계층엠만 의존 가지지 않고 계층 구조를 유연하게 적용하기도 함
    ex) 도메인과 응용 계층은 룰 엔진과 DB 연동을 위해 인프라스트럭처 모듈에 의존

주의할 점 : 표현, 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라스트럭처 계층에 종속됨
-> 테스트 어려움, 기능 확장의 어려움 발생
-> 이를 해결하는 것이 DIP


2.3 DIP

  • 고수준 모듈
    • 의미 있는 단일 기능을 제공하는 모듈 ex) 가격 할인 계산
    • 여러 하위 기능 필요 ex) 고객 정보 구하기 + 룰 실행
  • 저수준 모듈
    • 하위 기능을 실제로 구현한 모듈
      ex) JAP를 이용해서 고객 정보 읽어오는 모듈, Drools로 룰 실행하는 모듈
  • 고수준 모듈이 저수준 모듈 사용하면 앞에서 언급된 두 가지 문제 발생

  • DIP(Dependency Inversion Principle, 의존 역전 원칙)
    • 추상화 인터페이스를 이용해서 저수준 모듈이 고수준 모듈에 의존하도록 함
    • 고수준 모듈은 구현을 추상화한 인터페이스에 의존
    • 구현 교체의 어려움과 테스트의 어려움 해결 (p.73~75 참고)

public interface RuleDiscounter {
	Money applyRules(Customer customer, List<OrderLine> orderLines);
}
public class CalculateDiscountService {
	private RuleDiscounter ruleDiscounter;

	public Money calculateDiscount(List<OrderLine> orderLines, String customerId) {
		Customer customer = findCusotmer(customerId);
		return ruleDiscounter.applyRules(customer, orderLines);
	}
    ...
}
public class DroolsRuleDiscounter implements RuleDiscounter{
	private KieContainer kContainer;
	
	public DroolsRuleEngine() {
		KieServices ks = KieServices.Factory.get();
		kContainer = ks.getKieClasspathContainer();
	}
    
	@Override
	public Money applyRules(Customer customer, List<OrderLine> orderLines) {
		KieSession kSession = kContainer.newKieSession("discountSession");

    	try {
      	...
      	kSession.fireAllRules();
    	} finally {
      	kSession.dispose();
    	}
    	return money.toImmutableMoney();
	}
}

2.3.1 DIP 주의사항

-> DIP를 잘못 적용한 예

  • DIP는 단순히 인터페이스와 구현 클래스를 분리하는 것이 아님
  • 하위 기능을 추상화한 인터페이스는 고수준 모듈에 위치해야 함


2.3.2 DIP와 아키텍처

  • 아키텍처에 DIP를 적용하면 저수준 모듈인 인프라스트럭처 영역이 고수준 모듈인 응용 영역과 도메인 영역에 의존(상속)하는 구조가 됨
  • 도메인과 응용 영역에 대한 영향을 주지 않거나 최소화하면서 인프라스트럭처 영역에서 구현 기술을 변경 가능
  • 예시

2.4 도메인 영역의 주요 구성요소

요소설명
엔티티(ENTITY)고유의 식별자를 갖는 객체로 자신의 라이프사이클을 가짐. 도메인의 고유한 개념을 표현함. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공.
밸류(VALUE)고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나인 도메인 객체의 속성을 표현할 때 사용됨. 엔티티의 속성으로 사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용 가능
애그리거트(AGGREGATE)연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것. ex) 주문과 관련된 Order 엔티티, OrderLine 밸류, Orderer 밸류 객체를 '주문' 애그리거트로 묶을 수 있음
리포지터리(REPOSITORY)도메인 모델의 영속성을 처리함. ex) DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능 제공
도메인 서비스(DOMAIN SERVICE)특정 엔티티에 속하지 않은 도메인 로직을 제공. 도메인 로직이 여러 엔티티와 밸류를 필요로 할 경우 도메인 서비스에서 로직을 구현함.

2.4.1 엔티티와 밸류

  • 도메인 모델의 엔티티가 DB 모델의 엔티티와 다른 점
    1. 데이터와 함께 도메인 기능을 함꼐 제공
      -> 도메인 관점에서 기능 구현, 기능 구현을 캡슐화해서 데이터가 임의로 변경되지 않도록 방지
    2. 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현 가능
      -> 도메인을 보다 잘 이해할 수 있음

2.4.2 애그리거트

  • 애그리거트(AGGREGATE)
    • 관련 객체를 하나로 묶은 군집
    • 도메인 모델에서 전체 구조를 이해하도록 도움을 줌
    • 도메인이 커질수록 도메인 모델의 구성요소가 복잡해져 발생하는 전체적인 모델 관리의 어려움을 애그리거트가 해결

  • 루트 엔티티
    • 군집에 속한 객체 관리
    • 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능 제공
    • 애그리거트 사용하는 코드는 애그리거트 루트가 제공하는 기능 실행 및 애그리거트 루트를 통해서 간접적으로 애그리거트 내의 다른 엔티티나 밸류 객테에 접근
    • 애그리거트 단위로 구현을 캡슐화

2.4.3 리포지터리

  • 리포지터리
    • 물리적인 저장소에 도메인 객체 보관할 때 사용되는 도메인 모델
    • 애그리거트 단위로 도메인 객체 저장하고 조회하는 기능 정의
  • 리포지터리를 통해서 도메인 객체 구한 뒤에 도메인 객체의 기능 실행
public class CancelOrderService {
	private OrderRepository orderRepository;
    
    public void cancel(OrderNumber orderRepository) {
    	Order order = orderRepository.findOrderById(number);
        if(order == null) 
        	throw new OrderNotFoundException(orderRepository);
        order.cancel();
    }
    ...
}
  • 리포지터리 인터페이스는 도메인 객체를 영속화하는 데 필요한 기능을 추상화한 것 -> 고수준 모듈 (도메인 모델 영역에 속함)

  • 리포지터리를 구현한 클래스는 저수준 모듈 (인프라스트럭처 영역에 속함)

  • 응용 서비스와 리포지터리의 관계

    • 응용 서비스는 필요한 도메인 객체를 구하거나 저장할 때 리포지터리 사용
    • 응용 서비스는 트랜잭션을 관리하는데, 트랜잭션 처리는 히포지터리 구현 기술의 영향 받음
    • 리포지터리는 응용 서비스가 필요로 하는 메서드 제공
      ex) 기본적으로 save(some), findById(id) + delete(id)sk counts() 등도 제공

2.5 요청 처리 흐름

  1. 사용자가 애플리케이션에 기능 실행 요청하면 표현 영역이 그 요청을 받음
    • 표현 영역은 사용자가 전송한 데이터 형식이 올바른지 검사
    • 문제 없으면 응용 서비스가 요구하는 형식으로 데이터 변환해서 응용 서비스에 전달 및 기능 실행 위임
    • 스프링 MVC의 경우 컨트롤러가 요청을 받아서 처리
  2. 응용 서비스도메인 모델을 이용해서 기능 구현
    • 기능 구현에 필요한 도메인 객체를 리포지터리에서 가져와 실행하거나 신규 도메인 객체 생성해서 저장
    • 도메인의 상태 변경이 물리 저장소에 올바르게 반영되도록 트랜잭션 관리 (스프링의 경우 @Transactional 애너테이션 사용)

2.6 인프라스트럭처 개요

  • 인프라스트럭처는 다른 영역에서 필요로 하는 프레임워크, 구현 기술, 보조 기능 지원
    ex) 도메인 객체의 영속성 처리, 트랜잭션, SMTP 클라이언트, REST 클라이언트 등

  • 인프라스트럭처에 대한 의존을 없애면(DIP) 시스템 더 유연하고 테스트 쉬워짐

  • 하지만 DIP의 장점과 더불어 구현의 편리함을 생각하여 구현 기술에 대한 의존을 적절히 갖도록 해야 함

    • ex) 스프링의 @Transactional 애너테이션 -> 스프링에 대한 의존 없애면 복잡한 스프링 설정 사용해야 함

2.7 모듈 구성

  • 아키텍처의 각 영역은 별도 패키지에 위치
ui -> application -> domain <- infrastructure
  • 도메인이 클 경우 하위 도메인으로 나누고 각 하위 도메인마다 별도 패키지 구성

  • 도메인 모듈은 도메인에 속한 애그리거트를 기준으로 다시 패키지 구성

  • 애그리거트, 모델, 리포지터리는 같은 패키지에 위치

  • 모듈 구조에 대해 정해진 규칙은 없음
  • 코드를 찾을 때 불편한 정도만 아니면 됨
  • 가능하면 한 패키지에 10~15개 미만의 타입 개수 유지.

0개의 댓글

관련 채용 정보