DDD : 6. 어플리케이션 서비스

     

     

    6.1 어플리케이션 서비스란 무엇인가?

    • 서비스 계층에는 어플리케이션 서비스 / 도메인 서비스가 존재함.
      • 도메인 서비스는 도메인 규칙이나 도메인 객체에서 표현하기 애매한 규칙들을 모아놓은 곳임. 
        • (사용자 아이디는 특수 문자를 포함할 수 없음)
      • 어플리케이션 서비스는 어플리케이션이 동작하기 위한 동작등을 구현하는 곳임.
        • 사용자 가입, 사용자 탈퇴, 사용자 정보 수정

    도메인 명세를 도메인 객체에 코드로 나타낸 것만으로는 어플리케이션이 만들어지지 않는다. 도메인 규칙을 옮겨놓은 것일 뿐이기 때문이다. 동작하는 어플리케이션을 만들기 위해서는 '어플리케이션에서 정의하는 행동'을 구현하는 '어플리케이션 서비스' 계층을 구현해야 한다. 

     

     


    6.2 Usecase 수립하기  → 어플리케이션 서비스

    • 사용자 가입하기
    • 사용자 탈퇴하기
    • 사용자 정보 수정하기 
    • 게시글 등록하기

    간단한 게시판 웹서비스를 하나 개발한다고 해보자. 어플리케이션 서비스는 유즈케이스 (사용자 기능)을 구현하는 계층이다. 어플리케이션 서비스에 올 수 있을만한 기능은 위와 같은 녀석들일 것이다. 

    그렇다면 어플리케이션 서비스와 별개로 도메인 규칙은 어디에 각각 등록해 볼 수 있을까? 

    • 사용자 가입하기
      • 도메인 규칙1. 사용자 이름은 특수문자가 포함되면 안됨 → 도메인 클래스 (엔티티, 값객체)
      • 도메인 규칙2. 중복된 사용자 이름은 안됨. → 도메인 서비스

    도메인 규칙1,2가 있을 때 1번 규칙은 도메인 클래스에 명세해야 할 것이고, 2번 규칙은 도메인 서비스에 명세해야할 것이다. 그리고 어플리케이션 서비스는 도메인 서비스를 주입받아 도메인 규칙을 체크하는 형식으로 유즈케이스를 구현할 것이다.

    @RequiredArgsConstructor
    class UserApplicationService{
    	
        private final UserDomainService userDomainService;
        private final UserRepository userRepository;
        
        public void register(String userName){
        
        	if (userDomainService.exist(userName)){
            	// 도메인 서비스에서 도메인 확인
                ...
            }
        
        	userRepository.save(new User(userName));
        	...
        }
    	...
    }

    위 코드처럼 어플리케이션 서비스 내에서 도메인 서비스를 호출해 도메인 규칙을 확인하고, Repository 계층에게 DB 접근을 요청하는 형태로 동작하게 될 것이다. 

     


    6.3 도메인 객체는 서비스 계층 밖으로 던져도 될까? 

    어플리케이션 서비스 계층에서 도메인 서비스 / 도메인 객체를 직접 다른다. 결과물은 도메인 객체로 만들어질텐데, 어플리케이션 서비스의 메서드 호출 결과로 도메인 객체를 반환하는 것, 반환하지 않는 것은 각각 어떤 장단점이 있을까? 

    • 도메인 객체 직접 반환
    • DTO 객체 반환

     

    도메인 객체 직접 반환

    • 장점 
      • 어플리케이션 서비스 코드가 간단해짐. 
      • 클라이언트가 요청하는 반환값이 바뀌더라도 서비스 계층까지 영향을 미치지 않음.
    • 단점
      • 어플리케이션 서비스 밖에서도 도메인 객체의 메서드 호출이 가능해 짐. 
      • 도메인 객체에 변경이 발생하면, 변경지점이 더욱 넓어짐. 

     

    DTO 객체 반환

    • 장점
      • 도메인 객체 사용 범위가 강제되면서, 도메인의 값 변화에 좀 더 둔감하게 반응함. 
    • 단점
      • DTO 객체가 만들어지므로 오버헤드 발생. 
      • DTO 객체를 만드는 코드를 작성해야 함. (이 코드는 길어질 수 있음. 

     


    6.4 도메인 규칙의 유출 방지 필요

    • 도메인 규칙은 도메인 객체 / 도메인 서비스 계층에만 존재해야함. 
    • 도메인 규칙이 어플리케이션 서비스 계층에 유출되면 안됨. 

    도메인 규칙이 어플리케이션 서비스 계층에 유출되면, 도메인 규칙 변경 시 변경점을 반영해야 할 범위가 굉장히 넓어지게 된다. 이 경우 변경에 취약스러워진다는 것이다. 

    // 어플리케이션 서비스
    class UserApplicationService{
    	...
       	public void register(String username){
        	// 중복 아이디를 가지면 안된다는 도메인 규칙
            User user = userRepository.find(userName);
            if (user != null){
            	throw new RuntimeException(...);
            }
        	...
        }
        
        public void update(String username){
        	// 중복 아이디를 가지면 안된다는 도메인 규칙
            User user = userRepository.find(userName);
            if (user != null){
            	throw new RuntimeException(...);
            }
        	...
        }
    	...
    }

    위 코드는 중복 아이디를 가지면 안된다는 도메인 규칙 로직이 어플리케이션 서비스에 직접 구현되어있다. update(), register() 메서드에 도메인 규칙이 각각 선언되어있다. 만약 중복 아이디를 가지면 안된다는 규칙이 중복 이메일은 안되는 것으로 바뀌면 개발자는 register(), update() 메서드에서 각각 수정해야한다. 그런데 이런 메서드들이 많다면 놓치는 부분이 발생할 확률이 커진다.

    // 어플리케이션 서비스
    class UserApplicationService{
    	...
       	public void register(String username){
        	// 중복 아이디를 가지면 안된다는 도메인 규칙 → 도메인 서비스에 위임.
            if (userDomainService.isExist()){
            	...
            }
        }
        
        public void update(String username){
        	// 중복 아이디를 가지면 안된다는 도메인 규칙 → 도메인 서비스에 위임.
            if (userDomainService.isExist()){
            	...
            }
        	...
        }
    	...
    }

    이를 해결하기 위해 도메인 규칙은 도메인 서비스 계층에 두고, 어플리케이션 서비스가 도메인 서비스에 위임하는 식으로 코드를 작성해야한다. 이렇게 작성하면 변경지점이 하나로 줄어들게 된다. 

    도메인 규칙이 도메인 서비스, 도메인 클래스 외로 유출되게 되면 그만큼 '도메인의 변경'에 영향을 받는 '코드'가 많아지게 된다. 영향 범위가 넓어지면 코드 수정 시, 실수할 가능성이 높아진다. 

     


    6.5 어플리케이션 서비스와 프로그램의 응집도

    • 응집도가 높다는 것은 모듈이 단일 책임원칙을 준수하고 있음을 의미한다. 
    • 응집도가 높은 코드는 가독성, 견고성, 재사용성이 좋다.

    • 위는 응집도가 상대적으로 낮은 클래스다. 
      • MethodA는 Value1, Value2만 집중. MethodB는 Value3, Value4만 집중함.
      • MethodA, Value1, Value2와 MethodB, Value3, Value4는 서로 상관이 없는 것들로 볼 수 있음.

    Class A, Class B로 분리

    상관없는 메서드들을 클래스 단위로 분리하면 각 클래스는 '응집도 높은 클래스'가 된다. 그렇다면 여기서 생각해봐야 할 점은 '무조건 응집도를 높이는 것이 좋은가?'이다. 답은 '때마다 다르다'이다. 

     

     

    다음 경우를 고려해보자.

    • 상대적 응집도 낮음 : 사용자 가입, 수정, 탈퇴, 조회를 담당하는 클래스 
    • 상대적 응집도 높음 : 사용자 가입 클래스 / 사용자 조회 클래스

    사용자 가입 클래스만 보면 사용자가 어떻게 가입하는지는 명확히 확인할 수 있지만, '사용자 전체'를 아우르는 컨텍스트에 대해서는 알기 어렵다. 개인적인 생각으로는 '필요한만큼의 컨텍스트를 알 수 있을 정도의 응집도'는 갖춰야 하는 것 같다. 그리고 그것이 오히려 가독성이 좋다고 생각한다. 


    6.6 어플리케이션 서비스의 인터페이스

    • 어플리케이션 서비스 계층에 인터페이스가 필요한 경우
      • 서비스 인터페이스를 정의하면 클라이언트 측의 편의성이 좋아짐. 
      • 서비스 구현체가 없더라도 서비스 인터페이스를 목업해서 미리 클라이언트 구현을 진행할 수 있기 때문임. 

     


    6.7 서비스란 무엇인가

    • 서비스는 다른 사람을 위한 행동을 하는 객체다.
      • 도메인 서비스는 '중복된 이름'이 있는지 확인하는 행동을 했다. 이 행동은 도메인 서비스를 위한 것이 아니라 '엔티티'의 도메인 규칙을 지키기 위해 하는 행동이었다. 
      • 어플리케이션 서비스는 회원 가입 처리를 했다. 이것은 어플리케이션, 클라이언트를 위한 활동이었다.
    • 값객체, 엔티티는 자기 자신을 위한 행동을 하는 객체다. 

    정리하면 서비스는 '누군가를 위한 행위'를 하는 객체이며 도메인 규칙을 명세하느냐, 어플리케이션 동작을 위하느냐에 따라 도메인 서비스와 어플리케이션 서비스가 정의된다. 

     

     

     


    6.8 정리

    • 도메인 모델을 도메인 서비스, 도메인 객체에 표현하는 것만으로 어플리케이션은 성립하지 않음. 
    • 어플리케이션 서비스는 도메인 객체를 이용해 Usecase를 구현함 
    • 어플리케이션 서비스에 도메인 규칙이 포함되지 않도록 조심해야 함. 
    • 도메인 규칙이 포함되면 도메인 규칙 변경점 발생 시 변경해야 하는 부분이 넓어지기 때문임. 
    • 서비스에서 도메인 객체를 유출하느냐에 따른 장/단점이 존재함.
      • 도메인 객체 유출 → 코드는 깔끔해지나, 예상하지 않은 곳에서 도메인 메서드 호출되면서 도메인 영향범위 넓어짐. 
      • 도메인 객체 랩핑 → DTO 구현을 위한 구현량이 많아짐. 
    • 응집도 높은 클래스가 좋을 때도 있고, 응집도 낮은 클래스가 좋을 때도 존재함. 
      • 개인적인 생각으로는 '필요한만큼의 컨텍스트를 알 수 있을 정도의 응집도'는 갖춰야 하는 것 같다. 그리고 그것이 오히려 가독성이 좋다고 생각한다. 
      • 응집도 낮음 : 사용자 가입, 조회, 탈퇴, 수정 전체를 담당하는 클래스
      • 응집도 높음 : 사용자 가입 클래스.

     

     

     

     

     

     

     

     

     

    댓글

    Designed by JB FACTORY