2carrot84
by 2carrot84
7 min read

Categories

  • book

Tags

  • DDD
  • Domain Driven Design
  • 도메인 주도 개발 시작하기

지난번 포스팅에 이어 최범균님의 도메인 주도 개발 시작하기 책 챕터 5,6 에 대해 정리한 내용을 포스팅 하려고 합니다.

chapter 5. 스프링 데이터 JPA 를 이용한 조회 기능
chapter 6. 응용 서비스와 표현 영역

지난번 포스팅은 아래 링크에서 보실 수 있습니다.

[책] 도메인 주도 개발 시작하기 - Chapter 1,2
[책] 도메인 주도 개발 시작하기 - Chapter 3,4

chapter 5. 스프링 데이터 JPA 를 이용한 조회 기능

5.1 시작에 앞서

  • CQRS 는 명령 모델과 조회 모델을 분리하는 패턴이다.

5.2 검색을 위한 스펙

  • 목록 조회와 같은 기능은 다양한 검색 조건을 조합해야 할 때가 있다.
    • 필요한 조합마다 find 메서드를 정의하는 것은 좋은 방법이 아니다.
  • 검색 조건을 다양하게 조합해야 할 때 사용할 수 있는 것이 스펙 이다.
    • 애그리거트가 특정 조건을 충족하는지 검사할 때 사용하는 인터페이스다.

5.3 스프링 데이터 JPA 를 이용한 스펙 구현

  • 스프링 데이터 JPA 는 검색 조건을 표현하기 위한 인터페이스인 Specification 을 제공한다.
  • 정정 메타 모델은 @StaticMetamodel 애너테이션을 이용해서 관련 모델을 지정한다.

5.4 리포지터리 / DAO 에서 스펙 사용하기

  • 스펙을 충족하는 엔티티를 검색하고 싶다면 findAll 메서드를 사용하면 된다.
    • findAll() 메서드는 스펙 인터페이스를 파라미터로 갖는다.

5.5 스펙 조합

  • 스프링 데이터 JPA 가 제공하는 스펙 인터페이스는 스펙을 조합할 수 있는 두 메서드를 제공하고 있다.
    • 이 두 메서드는 and 와 or 다.
    • not() 메서드도 제공한다.
    • where() 메서드는 스펙 인터페이스의 정적 메서드로 null 을 전달하면 아무 조건도 생성하지 않는 스펙 객체를 리턴한다.

5.6 정렬 지정하기

  • 스프링 데이터 JPA 는 두 가지 방법을 사용해서 정렬을 지정할 수 있다.
    • 메서드 이름에 OrderBy를 사용해서 정렬 기준 지정
    • Sort 를 인자로 전달
  • 특정 프러퍼티로 조회하는 find 메서드 이름 뒤에 OrderBy 를 사용해서 정렬 순서를 지정할 수 있다.
    • findByOrdererIdOrderByNumberDesc
    • 정렬 기준 프러퍼티가 두개 이상이면 메서드 이름이 길어지는 단점이 있다.
    • 메서드 이름으로 정렬 순서가 정해지기 때문에 상황에 따라 정렬 순서를 변경할 수 없다.
  • 스프링 데이터 JPA 는 정렬 순서를 지정할 때 사용할 수 있는 Sort 타입을 제공한다.
    • 두개 이상의 정렬 순서를 지정하고 싶다면 and 메서드를 사용해서 두 Sort 객체를 연결하면 된다.

5.7 페이징 처리하기

  • 목록을 보여줄 때 전체 데이터 중 일부만 보여주는 페이징 처리는 기본이다.
    • 스프링 데이터 JPA 는 페이징 처리를 위해 Pageable 타입을 이용한다.
    • PageRequest.of() 메서드의 첫번째 인자는 페이지 번호를 두번째 인자는 한 페이지의 개수를 의미한다.
    • PageRequest 와 Sort 를 사용하면 정렬 순서를 지정할 수 있다.
  • Page 타입을 사용하면 데이터 목록 뿐만 아니라 조건에 해당하는 전체 개수도 구할 수 있다.
  • 처음부터 N 개의 데이터가 필요하다면 findFirstN 형식의 메서드를 사용할 수도 있다.
    • First 대신 Top 을 사용해도 된다. 뒤에 숫자가 없으면 한 개 결과만 리턴한다.

5.8 스펙 조합을 위한 스펙 빌더 클래스

  • 스펙 빌더를 사용해서 작성하면 코드 양은 if 블록을 사용할 때와 비슷한데 메서드를 사용해서 조건을 표현하고 메서드 호출 체인으로 연속된 변수 할당을 줄여 코드 가독성을 높이고 구조가 단순해진다.
    • and(), isHasText(), ifTrue() 메서드가 있는데 이외에 필요한 메서드를 추가해서 사용하면 된다.

5.9 동적 인스턴스 생성

  • JPA 는 쿼리 결과에서 임의의 객체를 동적으로 생성할 수 있는 기능을 제공하고 있다.
    • JPQL 의 select 절을 보면 new 키워드 뒤에 생성할 인스턴스의 완전한 클래스 이름을 지정하고 괄호 안에 생성자에 인자로 전달할 값을 지정한다.
  • 조회 전용 모델을 만드는 이유는 표현 영역을 통해 사용자에게 데이터를 보여주기 위함이다.
  • 동적 인스턴스의 장점은 JPQL 을 그대로 사용하므로 객체 기준으로 쿼리를 작성하면서도 동시에 지연/즉시 로딩과 같은 고민 없이 원하는 모습으로 데이터를 조회할 수 있다는 점이다.

5.10 하이버네이트 @Subselect 사용

  • 하이버네이트는 JPA 확장 기능으로 @Subselect 를 제공한다.
    • @Subselect 는 쿼리 결과를 @Entity 로 매핑할 수 있는 유용한 기능이다.
    • @Immutable, @Subselect, @Synchronize 는 하이버네이트 전용 애너테이션인데 이 태그를 사용하면 테이블이 아닌 쿼리 결과를 @Entity 로 매핑할 수 있다.
  • @Subselect 는 조회 쿼리를 값으로 갖는다.
    • @Subselect 를 사용하면 쿼리 실행 결과를 매핑할 테이블처럼 사용한다.
    • 뷰를 수정할 수 없듯이 @Subselect 로 조회한 @Entity 역시 수정할 수 없다.
  • @Immutable 을 사용하면 하이버네이트는 해당 엔티티의 매핑 필드/프러퍼티가 변경되도 DB에 반영하지 않고 무시한다.
  • @Synchronize 는 해당 엔티티와 관련되 테이블 목록을 명시한다.
    • 하이버네이트는 엔티티를 로딩하기 전에 지정한 테이블과 관련된 변경이 발생하면 flush 를 먼저한다.
  • @Subselect 를 사용해도 EntityManger#find(), JPQL, Criteria 를 사용해서 조회할 수 있다.
    • 스펙을 사용할 수 있다는 것도 포함이다.

chapter 6. 응용 서비스와 표현 영역

6.1 표현 영역과 응용 영역

  • 응용 영역과 표현 영역이 사용자와 도메인을 연결 시켜주는 매개체 역활을 한다.
    • 표현 영역은 사용자의 요청을 해석하여 응용 서비스를 실행한다.
    • 실제 사용자가 원하는 기능을 제공하는 것은 응용 영역에 위치한 서비스다.
    • 표현 영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다.
    • 응용 서비스를 실행한 뒤에 표현 영역은 실행 결과를 사용자에 알맞은 형식으로 응답한다.
      • HTML 이나 JSON 형식으로 응답할 것이다.
  • 응용 서비스는 표현 영역에 의존하지 않는다.
    • 기능 실행에 필요한 입력 값을 받고 실행 결과만 리턴하면 된다.

6.2 응용 서비스의 역활

  • 응용 서비스는 사용자가 요청한 기능을 실행한다.
    • 사용자의 요청을 처리하기 위해 리포지터리에서 도메인 객체를 가져와 사용한다.
  • 응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직 일부를 구현하고 있을 가능성이 높다.
    • 응용 서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다.
  • 응용 서비스는 트랜잭션 처리도 담당한다.
    • 도메인의 상태 변경을 트랜잭션으로 처리해야 한다.
  • 응용 서비스의 주요 역활로 접근제어와 이벤트 처리가 있다.

6.2.1 도메인 로직 넣지 않기

  • 도메인 로직은 도메인 영역에 위치하고 응용 서비스는 도메인 로직을 구현하지 않는다.
  • 도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생한다.
    • 코드의 응집성이 떨어진다는 것이다.
    • 여러 응용 서비스에 동일한 도메인 로직을 구현할 가능성이 높아진다.

6.3 응용 서비스의 구현

  • 응용 서비스는 표현 영역과 도메인 영역을 연결하는 매개체 역활을 하는데 이는 디자인 패턴에서 Facade 와 같은 역활을 한다.

6.3.1 응용 서비스의 크기

  • 응용 서비스는 보통 다음의 두가지 방법 중 한 가지 방식으로 구현한다.
    • 한 응용 서비스 클래스에 도메인의 모든 기능 구현하기
    • 구분되는 기능별로 으용 서비스 클래스를 따로 구현하기
  • 한 클래스에서 모두 구현하면 다음과 같은 모습을 갖는다.
    • 각 메서드를 구현하는데 필요한 리포지터리나 도메인 서비스는 필드로 추가한다.
    • 각 기능에서 동일 로직에 대한 코드 중복을 제거할 수 있다.
    • 한 서비스 클래스의 크기가 커진다.
      • 관련 없는 코드가 뒤섞여 코드를 이해하는데 방해가 된다.
  • 기능별로 서비스 클래스를 구현하는 방식은 한개 내지 2~3개의 기능을 구현한다.
    • 코드 품질을 일정 수준으로 유지하는데 도움이 된다.
    • 각 클래스별로 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코드에 영향을 받지 않는다.
    • 각 기능마다 동일한 로직을 구현할 경우 여러 클래스에 중복해서 동일한 코드를 구현할 가능성이 있다.
      • 별도 클래스에 로직을 구현해서 코드가 중복되는 것을 방지할 수 있다.

6.3.2 응용 서비스의 인터페이스와 클래스

  • 응용 서비스를 구현할 때 논쟁이 될 만한 것이 인터페이스가 필요한 지이다.
  • 인터페이스가 필요한 몇가지 상황이 있다.
    • 구현 클래스가 여러개인 경우
    • 런타임에 구현 객체를 교체해야 할 때
  • 인터페이스와 클래스를 따로 구현하면 소스 파일만 많아지고 구현 클래스에 대한 간접 참조가 증가해서 전체 구조가 복잡해진다.
    • 인터페이스가 명확히 필요하기 전까지는 불필요하다.
  • TDD 를 즐겨 하고 표현 영역부터 개발을 시작한다면, 응용 서비스의 인터페이스부터 작성하게 될 것이다.
  • 도메인 영역이나 응용 영역의 개발을 먼저 시작하면 응용 서비스 클래스가 먼저 만들어진다.
    • mockito 와 같은 테스트 도구는 클래스에 대해서도 테스트용 대역 객체를 만들 수 있기 때문에 응용 서비스에 대한 인터페이스 없이도 표현 영역을 테스트 할 수 있다.

6.3.3 메서드 파라미터와 값 리턴

  • 응용 서비스가 제공하는 메서드는 도메인을 이용해서 사용자가 요구한 기능을 실행하는데 필요한 파라미터 값을 전달 받아야 한다.
    • 요청 파라미터가 두 개이상 존재하면 데이터 전달을 위한 별도 클래스를 사용하는 것이 편리하다.
  • 응용 서비스의 결과를 표현 영역에서 사용해야 하면 응용 서비스 메서드의 결과로 필요한 데이터를 리턴한다.
  • 응용 서비스에서 애그리거트 자체를 리턴하면 도메인의 로직 실행을 응용 서비스와 표현 영역 두 곳에서 할 수 있게 된다.
    • 코드의 응집도를 낮추는 원인이 된다.
    • 응용 서비스는 표현 영역에서 필요한 데이터만 리턴하는 것이 기능 실행 로직의 응집도를 높이는 확실한 방법이다.

6.3.4 표현 영역에 의존하지 않기

  • 응용 서비스의 파라미터 타입을 결정할 때 주의할 점은 표현 영역과 관련된 타입을 사용하면 안된다는 점이다. (HttpServletRequest, HttpSession 등)
    • 응용 서비스만 단독으로 테스트하기가 어려워진다.
    • 표현 영역의 응집도가 깨지는 것이다. 코드 유지 보수 비용을 증가 시키는 원인이 된다.

6.3.5 트랜잭션 처리

  • 트랜잭션을 관리하는 것은 응용 서비스의 중요한 역활이다.
    • 프레임워크가 제공하는 트랜잭션 기능을 적극 사용하는 것이 좋다.

6.4 표현 영역

  • 표현 영역의 책임은 크게 다음과 같다.
    • 사용자가 시스템을 사용할 수 있는 흐름(화면)을 제공하고 제어한다.
      • 웹서비스의 표현 영역은 사용자가 요청한 내용을 응답으로 제공한다.
    • 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
      • 표현 영역은 사용자의 요청 데이터를 응용 서비스가 요구하는 형식으로 변환하고 응용 서비스의 결과를 사용자에게 응답할 수 있는 형식으로 변환한다.
    • 사용자의 세션을 관리한다.
      • 웹은 쿠키나 서버 세션을 이용해서 사용자의 연결 상태를 관리한다.

6.5 값 검증

  • 값 검증은 표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다.
    • 원칙적으로 모든 값에 대한 검증은 응용 서비스에서 처리한다.
  • 응용 서비스에서 각 값이 유효한지 확인할 목적으로 익셉션을 사용할 때의 문제점은 사용자에게 좋지 않은 경험을 제공한다는 것이다.
    • 응용 서비스에서 에러 코드를 모아 하나의 익셉션을 발생시키는 방법도 있다.
  • 스프링과 같은 프레임웤는 값 검증을 위함 Validator 인터페이스를 별도로 제공한다.
  • 표현 영역과 응용 서비스가 다음과 같이 역활을 나누어 검증을 수행할 수도 있다.
    • 표현 영역 : 필수 값, 값의 형식, 범위 등을 검증한다.
    • 응용 서비스 : 데이터의 존재 유무와 같은 논리적 오류를 검증한다.

6.6 권한 검사

  • 스프링 시큐리티 같은 프리임워크는 유연하고 확장 가능한 구조를 갖고 있다.
  • 표현 영역에서 할 수 있는 기본적인 검사는 인증된 사용자인지 아닌지 검사하는 것이다.
    • URL 을 처리하는 컨트롤러에 웹 요청을 전달하기 전에 인증 여부를 검사해서 인증된 사용자의 웹 요청만 컨트롤러에 전달한다.
    • 인증된 사용자가 아닐 경우 로그인 화면으로 리다이렉트 시킨다.
    • 이런 접근 제어를 하기에 좋은 위치가 서블릿 필터이다.

6.7 조회전용 기능과 응용 서비스

  • 서비스에서 수행하는 추가적인 로직이 없을 뿐더러 단일 쿼리만 실행하는 조회 전용 기능은 굳이 서비스를 만들 필요 없이 표현 영역에서 바로 조회 전용 기능을 사용해도 문제가 없다.

마무리

그럼 이만. 🥕👋🏼🖐🏼

참고자료🤣

도메인 주도 개발 시작하기