DaehunGwak / study-start-ddd

'도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지' 책 스터디
5 stars 3 forks source link

2주차 - 02. 아키텍처 개요 #3

Open DaehunGwak opened 1 year ago

DaehunGwak commented 1 year ago

진도

  1. 아키텍처 개요

일정

kdg0209 commented 1 year ago

요약

https://kdg-is.tistory.com/450

궁금증

  1. 페이지 76P : DIP의 주의사항 중 추상화를 통해 생성된 인터페이스를 인프라 영역이 아닌 도메인 영역에 위치해야한다고 하는데 고수준 모듈에 위치해야 하는 이유는 무엇인지 모르겠습니다. 제가 생각하기로는 추상화를 통해 접근하기만 하면 되지 않을까? 위치까지 고려해야하나? 라는 의문이 듭니다.
  2. 페이지 77P : DIP와 아키텍처의 내용 중 인프라스트럭처의 영역은 구현 기술을 다루기 때문에 저수준 모듈이고 이에 DIP를 적용하면 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존(상속)하는 구조가 된다는데 의미가 와 닿지 않습니다.
DaehunGwak commented 1 year ago

요약

느낀점

동균(@kdg0209)님의 궁금증에 대한 저의 의견

의견으로만 바라봐 주시고 이상하면 토론해주시면 감사하겠습니다 :)

  1. 페이지 76P : DIP의 주의사항 중 추상화를 통해 생성된 인터페이스를 인프라 영역이 아닌 도메인 영역에 위치해야한다고 하는데 고수준 모듈에 위치해야 하는 이유는 무엇인지 모르겠습니다. 제가 생각하기로는 추상화를 통해 접근하기만 하면 되지 않을까? 위치까지 고려해야하나? 라는 의문이 듭니다.
  1. 페이지 77P : DIP와 아키텍처의 내용 중 인프라스트럭처의 영역은 구현 기술을 다루기 때문에 저수준 모듈이고 이에 DIP를 적용하면 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존(상속)하는 구조가 된다는데 의미가 와 닿지 않습니다.
// 응용 계층
public class PaymentService {
    private final PaymentRepository paymentRepository;

    // 여기서 어떤 구현체를 쓸건지 주입받아 결정되므로 의존성 역전이 이루집니다.
    // 네이버 구현체를 쓰고싶다면 Qualifer 혹은 생성자 아규먼트 이름을 통해 다른 구현체 빈을 주입 받으실 수 있습니다.
    public PyamentService(@Qualifer("kakaoPaymentRepository") PaymentRepository paymentRepository) {
        this.paymentRepository = paymentRepository; 
    } 

    public void pay(...) {
        this.paymentRepository.pay(...);
    }
}

//  도메인 인터페이스
public interface PaymentRepository {
    void pay(...);
}

// 도메인 인터페이스 구현체 (인프라스트럭처)
// 네이버 구현체
public class NaverPaymentRepository implements PaymentRepository {
    public void pay(...) {
        // naver 관련 결제 api 구현
    };
}

// 카카오 구현체
public class KakaoPaymentRepository implements PaymentRepository {
    public void pay(...) {
        // kakao 관련 결제 api 구현
    };
}
chilling1000 commented 1 year ago
  1. DDD에서 응용 영역(Application Layer)는 우리가 흔히 사용하던 비즈니스 로직을 직접 수행하는 영역이라기 보다는 도메인 레이어에 로직을 위임하는 역할을 수행한다. 따라서, 비즈니스 로직을 추상화하는 계층이라고도 볼 수 있다.

    • 물론, Repository를 사용하기에 서비스 역할을 충실히 한다고 볼 수 있으며, 유즈케이스에 따라, 트랜잭션을 선언할 수도 있고 안 할 수도 있다.
    • 후에 배울 이벤트도 서비스에서 호출하기에 여전히 중요한 역할을 하는 건 이전과 같다.

      @Service
      @RequiredArgsConstructor
      public class MissileProductionService {
      private final MissileRepository missileRepository;
      private final MissileComponentsInspector inspector;
      private final ApplicationEventPublisher publisher;
      
      @Transactional
      public Missile produceMissile(Missile missileComponents) {
          inspector.inspectMissileComponents(missileComponents);
      
          Missile missile = missileRepository.save(missileComponents);
      
          missile.testSimulation();
      
          publisher.publishEvent(new MissileProducedEvent(missile));
      
          return missile;
      }
  2. 구현 영역(Infrastructure Layer)는 우리가 그동안 사용해온 리포지토리 역할과 큰 차이점은 없다. 이전처럼 외부 시스템인 JDBC, JPA 등 여러 가지를 Infrastructure Layer에서 구현을 한다. 그래서 이벤트와 결합된 시스템을 구현할 때, 이 이벤트는 보통 외부 시스템을 사용한다. 그래서 아래와 비슷하다고 볼 수 있다.

    @Component
    @RequiredArgsConstructor
    public class MissileHandler {
        private final Notifier notifier; <- Infrastructure Layer에서 가져온 것
    
        @EventListener
        public void handle(MissileLaunchedEvent event) {
            notifier.notify(event.getTestResult);
        }
  3. 계층 구조에서 상위 계층은 하위 계층에 의존하지만, 하위 계층이 상위 계층에 의존하지 않는다.

      1. 표현 -> 2. 응용 -> 3. 도메인 -> 4. 인프라스트럭쳐 로 이루어져 있다면, 최상위 계층은 표현이며, 최하위는 인프라스트럭쳐이다.
    • 따라서, 응용이 표현을 의존할 수 없다. 표현이 응용을 의존할 뿐이다.
  4. 하지만, 3번처럼 엄격하게 계층 구조를 따른다면, 구현에 대한 어색함이 발생할 것이다. 우리가 원래대로 해왔던 흐름과 다르기 때문이다. 그래서 유연하게 바꿔서 사용하기도 한다. 예를 들면, 응용 계층은 도메인 계층에 의존하지만, 곧바로 인프라스트럭쳐 계층에 의존할 수 있다. 그렇기 때문에 서비스 클래스에 Repository 의존성을 주입받아 사용하게 된다. 만일 엄격하게 적용한다면, Repository 의존성은 도메인 계층에서 받아야 한다.

  5. 고수준 모듈과 저수준 모듈의 차이점은 추상과 명료다. 그래서 고수준은 추상적인 문제를 다루며, 저수준은 추상적인 문제를 토대로, 상세하게 문제를 해결한다. 우리가 인터페이스를 사용하는 가장 큰 목적이 바로 추상화다. DIP(의존성 역전 원칙)을 적용한다면, 저수준 모듈이 고수준 모듈에 의존할 수 있다.

    public interface Launcher {
        Result launch(Missile missile);
    }
    
    @Service
    @RequiredArgsConstructor
    public class LaunchMissileService {
        private final Launcher launcher;
    
        public boolean launchMissile(Missile missile) {
            Result result = launcher.launch(missile);
    
            if(result.isLaunched) return true;
            else return false;
        }
    }
    
    public class LaunchMissile implements Launcher {
    
        @Override
        public Result launch(Missile missile) {
            // 미사일 발사 행위
        }
    }

    위 코드를 보면, 인터페이스로 Missile 객체를 받아서 발사한다 라는 추상적인 행위를 만들었는데, 응용 계층에서 저수준의 모듈인 LaunchMissile 클래스를 바로 사용하지 않고, 인터페이스를 구현해서 처리한다. 직접적으로 의존을 하지 않게 된다는 뜻이다. 그로 인해 생기는 장점은 코드를 수정할 일이 생길 때, 수정하는 구간이 조금 더 줄여들고 쉬워진다. (SRP)

    *DIP 적용 시, 고수준과 저수준의 위치를 잘 알아야 한다. 인프라스트럭쳐 모듈 안에 인터페이스가 있으면, 그건 추상화 목적으로 선언되어 있지만, 결국에는 상위 모듈(도메인)이 하위 모듈(인프라스트럭쳐)를 의존하게 되는 셈이다. 따라서, 인터페이스는 상위 모듈에 위치해야 한다.

    *DIP는 하나의 방법일 뿐, 정답이 될 수 없다. 구현 기술이 여러 개로 나뉘고, 수정 사항이 발생했을 때의 상황 등 여러 가지 상황에 따라 적용해야 진가를 발휘한다.

  6. Aggregate를 나눌 때는 구조에 집중해야 한다. 주문과 결제가 동시에 이루어지는 앱이 있다면, 주문 및 결제가 동시에 생성되는 셈이니 하나의 Aggregate로 가는 게 맞을까? 정답은 없다. 하지만, 여러 가지 상황을 고려하여 나누거나 합칠 수도 있다. 여기서 결제 테이블에 OrderId를 가지고 있다면, 서로 연관이 있으므로, 주문 Aggregate에 포함하여, 모듈을 줄이는 방법도 있다. 주문/결제를 하나의 모듈일 때의 장점과 단점 그리고 각각의 모듈로 나눴을 때의 장점과 단점을 생각했을 때, 비교하면 된다. 간단한 예로, 유저 수가 많지도 않은 앱이 있는데 여기서 큰 규모로 발전할 가능성이 없고 그럴 개발 계획도 없다면, 주문과 결제를 나눈다는 게 오히려 비효율적이게 된다.

progress0407 commented 1 year ago

2장. 아키텍쳐 개요


네 개의 영역

계층 구조 아키텍처

상위 계층에서 하위 계층으로 의존만 존재. 하위 계층은 상위 계층에 의존하지 않는다.

인프라스터력처에 의존하면 테스트 어려움기능 확장의 어려움 두 가지 문제 발생. DIP를 통해서 해결 가능.

과연 테스트가 어려울까? 주관적인 해결법

  1. Mocking Library,
  2. 수동으로 만든 Fake 객체 이용 꼭 DIP를 이용해서가 아니라 인프라 객체를 상속해서 해결할 수 있음!

DIP

고수준 모듈이 단순히 저수준 모듈을 의존하고 있는 구조를 인터페이스, 추상 객체 등을 의존하게 하여 의존을 역전시키는 것.

image

다만 아래의 그림은 잘못된 구조라 한다.

image

인터페이스는 고수준 모듈 관점에서 도출해야 한다고 한다. Q) 그래야만 하는가?

DIP를 항상 적용할 필요는 없다.

DIP의 이점을 얻는 수준에서 적용 범위를 검토한다.

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

애그리거트

도메인이 커질 수록 많은 엔티티와 밸류가 출현. 개발자가 전체 구조를 집중하지 못하는 상황 발생. 지도로는 소축적 지도에 해당. 객체를 군집 단위로 파악할 때 도움이 된다.

요청 처리 흐름

인프라스턱쳐 개요

모듈 구성

그외

  1. 도메인이 인프라 갖기.
  2. 도메인의 반환이 DTO일 경우 문제?
developer-wonjin commented 1 year ago

https://github.com/developer-wonjin/Study/blob/master/%EA%B0%9C%EB%B0%9C%EB%B0%A9%EB%B2%95%EB%A1%A0/DDD/DDD%20START!%202%EC%9E%A5.md

2장. 아키텍쳐의 개요

Q. 스프링MVC프레임워크가 왜 표현영역인가?

표현영역은 Filter, DispatcherServlet, Handler, Controller 와 같은 서블릿계열에 해당하는 것으로 이해했기에

Q. 서비스영역에서 호출할 메소드가
   MVC중 (DAO, Mapper)에서 호출할 메소드와 1대1 매칭이 되는 경우... 나는 누구고 여긴 어디인가...

board
  └ src
     ├ member
     |   ├ service (≒서비스)
     |   ├ dao     (≒인프라스트럭쳐)
     |   └ model   (≒도메인)
     |
     └ article
     |   ├ service
     |   ├ dao
     |   └ model
     ├ reply
     |   ├ dao

인터페이스의 역할 : 구현객체 교체의 용이함을 위해서
 예) fly()  
  - 비행기의 엔진점화
  - 참새의 날갯짓
  위 두 가지의 구체적인 방식에는 관심없다.
  fly가 중요할 뿐.
  인터페이스가 없다면 flyByEngine(), flyByWing()과  같이 개별 메소드시그니처를 갖게됨
  객체교체시 클라이언트 코드의 코드수정이 요구되는 불편함 발생

4가지 영역

- 표현 영역

- 응용 영역

- 도메인 영역

-인프라스트럭쳐 영역

DIP

- 도입배경

서비스영역

public class CalculateDiscountService{
    private DroolsRuleEngine ruleEngine;

    public CalculateDiscountService(){
        ruleEngin = new DroolsRuleEngine();
    }

    public 리턴형 기능1();
    public 리턴형 기능2();
    public 리턴형 기능3();

}

인프라스트럭쳐영역

public class DroolsRuleEngine{
    ...어쩌구 저쩌구
    //구현기술이 담겨있음
}