티스토리 뷰

Spring

[Spring] 도메인 주도 설계 및 개발과 도메인 계층(도메인 객체 중심 개발)

망나니개발자 2021. 6. 2. 17:30
반응형

이 내용은 토비의 스프링 1권의 797부터 시작하는 내용을 참고하며 작성하였습니다.

 

 

1.  도메인 주도 설계 및 개발(도메인 객체 중심 개발)


[ 도메인 객체 중심 개발 ]

도메인 객체 중심 아키텍처란 도메인 모델을 반영하는 객체를 만들어두고, 그것을 중심으로 개발하는 아키텍처를 의미한다. 도메인 모델은 DB의 엔티티 설계에도 반영되기 때문에, DB의 엔티티와 유사할 확률이 높다. 물론 DB에는 없고 비즈니스 로직 상에서만 존재하는 모델도 있기 때문에 항상 일치하지는 않을 수 있다.

이렇게 객체를 만들어두고 그 안에 정보를 담아서 각 계층 사이에 전달하게 만드는 것이 도메인 객체 중심 아키텍처이다. 예를 들어 어떤 제품과 카테고리가 있을 때 다음과 같은 도메인 객체가 존재할 수 있다.

public class Category {
   private int id;
   private String name;
   private Set<Product> productList;
}

public class Product {
   private int id;
   private String name;
   private int price;
   private Category category;
}

 

위의 구조는 애플리케이션 어디에서도 사용될 수 있는 일관된 형식의 도메인 정보를 담고 있다. 그리고 이러한 구조는 객체 사이의 관계를 나타낼 수 있다. DB 또는 SQL 중심의 객체라면 Category에 대한 Id 컬럼이 있겠지만, 도메인 객체 중심 아키텍처에서 Product 클래스는 Category 객체에 대한 레퍼런스가 있다.

도메인 객체를 사용하면 비즈니스 로직에 대한 구현이 명확해진다. 예를 들어 어떤 카테고리에 포함된 모든 상품의 가격을 계산해야 한다면, 다음과 같은 로직을 서비스 계층에 만들어 주면 된다.

public int totalPriceOfCategory(Category category) {
   int sum = 0;
   for(Product product : category.getProducts()) {
       sum += product.getPrice();
   }
}

 

위와 같이 코드를 작성하면 다음과 같은 장점들을 얻을 수 있을 것이다.

  • 테스트를 작성하기에 용이하다.
  • 코드를 수정하기에 수월하다.
  • 재사용성이 높다.

이렇게 도메인 모델을 따르는 객체를 만들려면 DB에서 가져온 데이터를 도메인 객체에 맞게 변환해주어야 하는데, JPA나 Hibernate와 같은 ORM을 사용하면 이러한 부분을 자동으로 처리해준다.

연관된 도메인 객체가 사용될수도 그렇지 않을 수도 있다. 불필요한 데이터를 미리 조회하면 성능상의 문제가 생길 수 있는데, 이러한 이유로 JPA나 Hibernate 등은 Lazy Loading(지연 로딩) 기법을 지원하고 있다. 즉, Product 객체의 Category 객체는 처음에 존재하지 않다가, Category 객체를 사용할 때 객체를 DB에서 조회하는 것이다.

 

 

[ 빈약한 도메인 모델(객체) ]

하지만 위와 같이 도메인 객체에 정보만 담겨 있고 아무런 기능도 없는 것은 온전한 객체라고 보기 힘들다. 도메인 객체도 결국 자바 객체이기 때문에 속성과 행위를 가지고 있어야 하기 때문이다.

위와 같은 객체는 반쪽짜리 객체일 뿐이므로, 이러한 객체를 빈약한 객체라고 부른다.

그렇기 때문에 우리는 도메인 객체도 객체로 만들어 객체지향 프로그래밍할 수 있어야 한다. 해당 도메인 객체에 대한 비즈니스 로직이 있다고 하면 서비스 계층에 작성하는 것 보다, 해당 도메인에 작성하는 것이 좋다. 그리고 서비스 계층은 단순히 요청을 받아 도메인 객체에 이를 위임해주는 것이다. 대신 여러 종류의 도메인 객체를 조합해야 하거나 도메인 객체와 결합도가 떨어지는 비즈니스 로직(메일 발송 or 등) 또는 DAO 계층과 연동되어야 하는 로직 등은 서비스 계층에 작성하는 것이다.

비지니스 로직을 도메인 객체에 작성해줌으로써 얻는 장점들이 있다.

  • 객체지향스러운 개발을 할 수 있다.
  • 도메인 객체의 응집도를 높일 수 있다.
  • DI를 줄일 수 있다.
  • 서비스 계층의 코드가 간결하다.

 

1. 객체지향스러운 개발을 할 수 있다

일반적으로 객체는 속성과 기능을 가져야 한다. 그리고 객체들은 단순 속성(데이터)만 갖기 보다는 기능(함수)를 구현함으로써 메세지를 주고받아야 한다. 이러한 구현이 정말 객체지향스러운 개발이고, 이는 코드를 간결하게 해주며 가독성이나 이해를 쉽게 할 수 있도록 도와준다.

 

2. 도메인 객체의 응집도를 높일 수 있다

기존의 빈약한 객체를 기반으로 한다면 해당 객체와 관련된 비지니스 로직을 파악하기 위해 전체 서비스 레이어를 살펴봐야 한다. 하지만 해당 도메인 객체와 연관된 비지니스 로직을 파악하기 위해서는 해당 객체만 보면 된다.


3. DI를 줄일 수 있다

만약 비즈니스 로직이 서비스 계층에만 있다면 항상 해당 비즈니스 로직을 갖는 서비스 계층을 DI 해야 한다. 하지만 해당 비즈니스 로직이 객체에 있다면 불필요한 DI를 상당히 줄일 수 있다.

 

4. 서비스 계층의 코드가 간결해진다

이러한 방식으로 개발하면 도메인 객체가 스스로 처리가능한 비즈니스 로직을 갖고 있어 서비스 계층의 코드를 간결하게 유지할 수 있다. 그리고 이것은 코드 리딩이나 유지보수성 등을 높여줄 것이며 불필요한 중복 로직의 개발을 막을 수도 있다.

 

 

이러한 방식으로 개발을 하면 위의 코드는 다음과 같이 변하게 된다.

public class Category {
   private int id;
   private String desc;
   private Set<Product> products;

   public int totalPriceOfCategory() {
      int sum = 0;
      for(Product product : category.getProducts()) {
          sum += product.getPrice();
      }
   }
}

public class Product {
   private int id;
   private String name;
   private int price;
   private Category category;
}

 

 

2.  도메인 계층(Domain Layer) 방식


[ 도메인 계층 방식 ]

위의 내용을 살펴보면 느끼겠지만 결국 도메인 객체에 담을 수 있는 비즈니스 로직은 한계가 있을 수 있다.

그래서 도메인 객체가 스스로 필요한 정보는 DAO를 통해 가져오고, 생성이나 변경이 일어났을 때 직접 DAO에게 변경 사항을 반영해달라고 요청하는 등 도메인 객체가 기존의 3 계층 오브젝트를 DI 받아서 직접 이용할 수 있도록 하는 방법을 고민하게 되었다.

도메인 계층의 역할과 비중을 극대화하려다 보면 기존의 방식으로는 부족하기 때문에, 도메인 객체를 기존 3계층과 같은 레벨로 격상시켜 1개의 계층을 이루도록 하는 도메인 계층 방식이 등장하게 되었다.

즉, 도메인 객체들이 하나의 독립적인 계층을 이뤄서 서비스 계층과 데이터 액세스 계층 사이에 존재하는 것이다.

도메인 객체가 독립된 계층을 이루기 때문에 기존의 방식과 2가지 차이점을 갖게 된다.

  1. 도메인에 종속적인 비즈니스 로직은 서비스 계층이 아닌 도메인 계층의 객체가 처리한다.
  2. 도메인 객체가 기존 데이터 접근 계층이나 기반 계층의 기능을 직접 활용할 수 있다.

 

서비스 계층에서 사용자가 입력한 정보를 바탕으로 새로운 도메인 객체를 만들었든 데이터 액세스 계층을 통해 도메인 객체를 얻었든 상관없이 도매인 객체에게 비즈니스 로직의 처리를 요청한다. 해당 도메인 객체를 중심으로 만들어진 로직이라면 그 이후의 작업은 도메인 객체와 그 관련 오브젝트 사이에서 진행된다. 일단 도메인 계층으로 들어가면 서비스 계층의 도움 없이도 비즈니스 로직의 대부분을 수행할 수 있다는 것이다.

하지만 도메인 객체는 스프링의 빈이 아니기 때문에 AOP를 적용하여 다른 빈을 DI받도록 설정을 해주어야 한다. AspectJ AOP를 이용함으로써 클래스의 생성자가 호출되어 객체가 만들어지는 시점을 조인 포인트로 사용하여 다른 계층의 빈들을 DI해줄 수 있다.

도메인 계층 방식을 이용하면 도메인 객체가 데이터 액세스 계층 등을 이용할 수 있으며 기능의 제약이 사라진다. 물론 도메인 객체에 담긴 기능은 자신과 관련된 작업으로 한정되어야 한다.

그래서 도메인 객체에 많은 비즈니스 로직을 담을 수 있다. 그리고 서비스 로직은 여러 도메인 객체의 기능을 조합해야 하는 복잡한 작업을 처리하도록 하는 것이다. 즉, 다른 도메인 객체와 협력해야 하는 기능을 갖거나 단순한 위임의 역할을 하게 되는 것이다. 도매엔 객체를 독립적인 계층으로 만들 경우에는 도메인 오브젝트가 도메인 계층을 벗어나서도 사용되게 할지 말지를 고려해야 한다.

 

  • 여전히 모든 계층에서 도메인 객체를 사용한다.
  • 도메인 객체는 도메인 계층을 벗어나지 못하게 한다.

 

여전히 모든 계층에서 도메인 객체를 사용한다.

도메인 계층은 물론이고 서비스 계층이나 그 앞의 프레젠테이션 계층 등에서도 직접 도메인 객체를 받아 사용할 수 있도록 하는 것이다. 그러면 도메인 객체 중심의 아키텍처의 장점을 많이 얻어갈 수 있다. 하지만 도메인 객체가 핵심 비즈니스 로직이나 DB와 관련된 작업들을 갖고 있기 때문에 주의해서 사용해야 한다. 잘못 도메인 객체를 사용하면 심각한 혼란을 초래할 수 있기 때문이다. 그래서 AspectJ 등을 통해 잘못된 호출을 정책으로 만들어 두는 해결책을 적용할 수 있다.

 

 

도메인 객체는 도메인 계층을 벗어나지 못하게 한다.

도메인 계층 밖으로 전달될 때에는 정보 전달을 위한 DTO를 만들어 넘겨주는 것이다. DTO를 이용하면 안전하며 도메인 객체를 외부 계층의 코드로부터 보호할 수 있다. 반면에 도메인 객체와 유사한 객체를 따로 만들고 변환해야 한다는 번거로움이 있다.

도메인 계층은 기존의 3계층과 같은 계층에서 독립적인 역할을 담당하고 있다. 하지만 싱글톤으로 Spring 컨테이너에 의해 관리되지 않으며 짧은 시간 존재하고 사라진다. 도메인 객체는 상태정보를 갖기 때문에 사용자별 요청에 대해 독립적인 상태를 유지해야 하기 때문이다.

도메인 계층은 여러가지 제약과 불편을 갖고 있지만, 매우 복잡하고 변경이 잦은 도메인을 갖는 경우에 용이하다. 복잡한 도메인의 구조와 로직을 최대한 도메인 계층의 객체에 반영하고, 도메인 모델과 설계에 변경이 발생하면 빠르게 대응하여 변경하기 위해서다. 또한 도메인 계층은 응집도가 매우 높기 때문에 단위 테스트 작성도 용이하다.

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함