아키텍처

철근콘크리트·2020년 12월 31일
0

DDD

목록 보기
2/4

네개의 영역

아키텍처는 "표현", "응용", "도메인", "인프라스트럭처" 의 네 영역이다.

표현 영역

  • 응용 서비스가 리턴한 결과를 JSON 형식으로 변환해서 HTTP 응답으로 웹 브라우저에 전송한다.

응용 영역

  • 표현 영역을 통해 사용자의 요청을 전달받는 응용 영역은 시스템이 사용자에게 제공해야 할 기능을 구현한다.
  • '주문 등록', '주문 취소', '상품 상세 조회'와 같은 기능을 구현한다.
  • 응용 영역은 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다.


  • 주문 취소 기능을 예로 들면 다음과 같이 주문 도메인 모델을 사용하여 기능을 구현한다.
public class CancelOrderService {
	@Transactional
	public void cancelOrder(String orderId){
		Order order = findOrderById(orderId);
        	if(order == null) throw new OrderNotFoundException(orderId);
            	order.cancel();


        } 

}

도메인 영역

  • 도메인 모델은 도메인의 핵심 로직을 구현한다.
  • 주문 도메인의 경우 '배송지 변경', '결제 완료', '주문 총액 계산'과 같은 핵심 로직을 도메인 모델에서 구현한다.

인프라스트럭처

  • 이 영역은 RDBMS 연동을 처리하고, 메시징 큐에 메시지를 전송하거나 수신하는 기능을 구현하고, 몽고 DB나 HBase를 사용해서 데이터베이스 연동처리 한다.
  • 이 영역은 SMTP를 이용한 메일 발송 기능을 구현하거나 HTTP 클라이언트를 이용해서 REST API를 호출하는 것도 처리한다.
  • 인프라스트럭처 영역은 논리적인 개념을 표현하기보다는 실제 구현을 다룬다.

    도메인 영역, 응용 영역, 표현 영역은 구현 기술을 사용한 코드를 직접 만들지 않는다. 대신 인프라스트럭처 영역에서 제공하는 기능을 사용해서 필요한 기능을 개발한다.
    예를 들어, 응용 영역에서 DB에 보관된 데이터가 필요하면 인프라스트럭처 영역의 DB 모듈을 사용해서 데이터를 읽어온다.




계층 구조 아키텍처

응용 영역과 도메인 영역은 DB나 외부 시스템 연동을 위해 인프라스트럭처의 기능을 사용하므로 이런 계층 구조를 사용하는 것이 직관적으로 이해하기 쉽다. 하지만, 짚고 넘어가야 할 것이 있는데 바로 표현, 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라스트럭처 계층에 종속된다는 점이다.

🙏 도메인 가격 계산 규칙을 예로 들어보자. 할인 금액 계산 로직이 복잡해지면 객체 지향으로 구현하는 것 보다 "룰 엔진"을 사용하는 것이 알맞을 때가 있다.


Drools라는 룰 엔진 사용해서 로직 수행할 수 있는 인프라스트럭처 영역의 코드

public class CalculateDiscountService{

	private DroolsRuleEngine ruleEngine;
    
    	public CalculateDiscountService(){
        	ruleEngine = new DroolsRuleEngine();
        }
        
        public Money calculateDiscount(List<OrderLine> orderLines, String customerId){
    		Customer customer = findCustomer(customerId);
            
            
            	MutableMoney money = new MutableMoney(0);
                List<?> facts = Arrays.asList(customer, money);
                facts.addAll(orderLines);
                ruleEngine.evalute("discountCalculation", facts);
                return money.toImmutableMoney();
        
        	
	}

}

CalculateDiscountService가 겉으로는 인프라스트럭처의 기술에 직접적인 의존을 하지 않는 것처럼 보여도 실제로는 Drools라는 인프라스트럭처 영역의 기술에 완전하게 의존하고 있다. 이런 상황에서 Drools가 아닌 다른 구현 기술을 사용하려면 코드의 많은 부분을 고쳐야 한다..

"테스트 어려움""기능 확장의 어려움"이라는 문제가 발생한다.
이 두가지 문제를 해소 할수 있는 방법은 "DIP"를 적용하는 것이다.


🔥DIP

CalculateDiscountService는 고수준 모듈이다. 고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요하다.

고수준 모듈이 제대로 동작하려면 저수준 모듈을 사용해야 한다. 그런데, 고수준 모듈이 저수준 모듈을 사용하면 앞서 계층 구조 아키텍처에서 언급했던 두가지 문제(구현 변경과 테스트가 어려움)가 발생한다.

DIP는 이 문제를 해결하기 위해 저수준 모듈이 고수준 모듈에 의존하도록 바꾼다. ( 추상화 인터페이스를 사용하면 된다.)

public interface RuleDiscounter{
 	public Money applyRules(Custome customer, List<OrderLine> orderLines);
}

CalculateDiscountService가 RuleDiscounter를 이용하도록 변경

public class CalculateDiscountService{
	private RuleDiscounter ruleDiscounter; 
    
    public CalculateDiscountService(RuleDiscounter ruleDiscounter){
    	this.ruleDiscounter = ruleDiscounter;
    }

    public Money calculateDiscount(List<OrderLine> orderLines, String customerId){
    	Customer customer = findCustomer(customerId);
        return ruleDiscounter.applyRules(customer, orderLines); 
    }

}

-> CalculateDiscountService는 더 이상 구현 기술인 Drools에 의존하지 않는다.
룰을 이용한 할인 금액 계산을 추상화한 RuleDiscounter 인터페이스에 의존할 뿐이다.
"룰을 이용한 할인 금액 계산"은 고수준 모듈의 개념이므로 RuleDiscounter 인터페이스는 고수준 모듈에 속한다. DroolsRuleDiscounter는 고수준의 하위 기능인 RuleDiscounter를 구현한 것이므로 저수준 모듈에 속한다.

DIP를 적용하면 앞서 다른 영역이 인프라스트럭처 영역에 의존할 때 발생했던 두 가지 문제인 구현 교체가 어렵다는 문제와 테스트가 어려운 문제를 해소할 수 있다.

//사용할 저수준 객체 생성
RuleDiscounter ruleDiscounter = new DroolsRuleDiscounter();

//생성자 방식으로 주입
CalculateDiscountService disService = new CalculateDiscountService(ruleDiscounter);

//사용할 저수준 구현 객체 변경
RuleDiscounter ruleDiscounter = new SimpleRuleDiscounter();

//사용할 저수준 모듈을 변경해도 고수준 모듈을 수정할 필요가 없다. 
CalculateDiscountService disService = new CalculateDiscountService(ruleDiscounter);




DIP와 아키텍처

인프라스트럭처 영역은 구현 기술을 다루는 저수준 모듈이고 응용 영역과 도메인 영역은 고수준 모듈이다.


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

  • 엔티티 : 주문(Order), 회원(Member), 상품(Product)과 같이 도메인의 교유한 개념을 표현한다. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.

  • 벨류 : 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나인 도메인 객체의 속성을 표현할 때 사용된다.

  • 애그리거트 : 관련된 엔티티와 밸루 객체를 개념적으로 하나로 묶은 것이다. 예를 들어, 주문과 관련된 Order 엔티티, OrderLine밸류, Order 밸류 객체를 '주문' 애그리거트로 묶을 수 있다.

  • 리포지터리 : 도메인 모델의 영속성을 처리한다. 예를 들어, DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능을 제공한다.

  • 도메인 서비스 : 특정 엔티티에 속하지 않은 도메인 로직을 제공한다. '할인 금액 계산'은 상품, 쿠폰, 회원 등급, 구매 금액 등 다양한 조건을 이용해서 구현하게 되는데, 이렇게 도메인 로직이 여러 엔티티와 밸류를 필요로 할 경우 도메인 서비스에서 로직을 구현한다.

애그리커드를 사용하면 개별 객체가 아닌 관련 객체를 묶어서 객체 군집 단위로 모델을 바라볼 수 있다.
(개별 객체 간의 관계가 아닌 애그리거트 간의 관계로 도메인 모델을 이해하고 구현할 수 있게 되며, 이를 통해 큰 틀에서 도메인 모델을 관리할 수 있게 된다. )



> 리포지터리

  • 도메인 객체를 지속적으로 사용하려면 RDBMS, NoSQL, 로컬 파일과 같은 물리적인 저장소에 도메인 객체를 보관해야 한다. 이를 위한 도메인 모델이 리포지터리(repository)이다.
    (엔티티나 벨류가 요구사항에 도출되는 도메인 모델이라면 리포지터리는 구현을 위한 도메인 모델이다. )

응용 서비스와 리포지터리

  • 응용 서비스는 필요한 도메인 객체를 구하거나 저장할 때 리포지터리를 사용한다
  • 응용 서비스는 트랜잭션을 관리하는데, 트랜잭션 처리는 리포지터리 구현 기술에 영향을 받는다.
public class CancelOrderService{
	private OrderRepository orderRepository;
    
    	@Transaction //응용서비스는 트랜잭션을 관리한다.
	public void cancel(OrderNumber number){
    		Order order = orderRepository.findByNumber(number);
            	if(order == null) throw new NoOrderException(number);
                order.cancel();
    	}

}

표현 영역

  • 사용자가 전송한 데이터 형식이 올바른지 검사하고 문제가 없다면 데이터를 이용해서 응용 서비스에 기능 실행을 위임한다.

인프라스트럭처

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

  • DIP에서 언급한 것처럼 도메인 영역과 응용 영역에서 인프라스트럭처의 기능을 직접 사용하는 것보다 이 두 영역에 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 시스템을 더 유연하고 테스트하기 쉽게 만들어 준다.

  • 하지만 무조건 인프라스트럭처에 대한 의존을 없애는 것이 좋은 것이 아니다. (스프링은 @Transactional / JPA는 @Entity나 @Table과 같은 JPA 전용 애노테이션을 도메인 모델 클래스에 사용하는 것이 효율적이다.)

  • 응용 영역과 도메인 영역이 인프라스트럭처에 대한 의존을 완전히 갖지 않도록 시도하는 것은 자칫 구현을 더 복잡하고 어렵게 만들 수 있다.





https://www.slideshare.net/cc612/ddd-start-2

0개의 댓글