객체지향

[DDD, 도메인 주도 개발] 6.응용서비스와 표현영역

마디니 2023. 4. 9. 22:21
반응형

응용 서비스

응용 서비스 역할

  • 표현 영역과 도메인 영역을 연결하는 매개체 
  • 사용자가 요청한 기능을 실행한다. -> 이 처리를 위해 리포지터리에서 도메인 객체를 가져와 사용한다.
  • 도메인 같의 객체 흐름을 담당하기 때문에 다음과 같이 단순한 형태를 갖는다.
//1. 리포지터리에서 애그리거트를 구한다.
//2. 애그리거트의 도메인 기능을 실행
//3. 결과를 리턴
  • 트랜잭션 처리도 담당하여 데이터 일관성이 깨지지 않도록 한다. -> 스프링 프레임워크가 제공하는 @Transactional로 쉽게 처리 가능

"응용 서비스가 복잡하다는 생각이 든다면?"

도메인 기능을 구현하고 있는지 체크 해볼 것

다음과 같이 비밀번호 변경 기능을 담당하는 ChangePasswordService 클래스가 있다. 

/* 서비스에 도메인(암호 확인)로직 로직이 구현되어 있다 */
public class ChangePasswordService {
	
	public void changePassword(String memberId, String oldPw, String newPw) {
		Member member = memberRepository.findById(memberId);
		checkMember(member);
			
		if (!passwordEncoder.matches(oldPw, member.getPassword()) {
			throw new BadPasswordException();
		}
		
		member.setPassword(newPW);
	}

}

public class Member {
	public void setPassword(String newPw) {
		if (isEmpty(newPw)) throw new IllegalArgumentException("no new password");
		this.password = newPw;
	}
}

[위 코드의 문제점 >> 코드 변경을 어렵게 한다]

  • 코드의 응집성을 떨어뜨린다. -> 데이터와 데이터를 조작하는 로직이 다른영역에 있기 때문에 도메인 로직을 파악하기 위해 여러 영역을 분석해야 한다.
  • 여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.  -> 암호확인 로직이 필요한 모든 서비스에 중복 로직을 만들게 된다.

응용 서비스 구현

방법 1. 하나의 응용서비스 클래스에 회원 도메인의 모든 기능 구현하기

  • 한 도메인과 관련된 모든 코드가 한 클래스에 있다.
  • 동일 로직에 대한 중복을 제거 할 수 있다.
  • [단점] 한 서비스 클래스 크기가 커진다. >>> 연관성 적은 코드가 함께 위치할 가능성이 높아지게 된다. >>> 코드 이해도가 낮아진다

방법 2. 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기

  • 한 응용서비스 클래스에서 1~2,3개 기능 구현하는 방법
  • 클래스 개수는 많아지지만 코드 품질수준을 유지하는데 도움이 된다.
  • 필요한 의존 객체만 포함하게 되어 독립성이 높아진다.
  • [단점]  여러 클래스에 중복해서 동일한 코드를 구현할 가능성이 있다. >> 별도 클래스에 구현해서 중복을 방지할 수 있다.

 

"응용 서비스에 인터페이스가 필요할까?"

구현 클래스가 여러개인 경우나 런타임에 구현 객체를 교체해야 할 때 인터페이스가 필요하지만 응용서비스에서는 이런 상황이 드물다.

인터페이스가 명확하게 필요하기 전까지는 굳이 만들지 않아도 되지 않을까?

(테스트 주도개발에서 표현영역부터 개발을 시작한다면) 응용서비스 인터페이스 작성이 필요할 수 있지만

도메인 영역이나 응용영역의 개발을 먼저 시작하면 단위 테스트를 위한 응용 서비스 클래스의 가짜 객체는 Mockito와 같은 대역객체를 사용할 수 있다.

 

응용 서비스 파라미터와 값 리턴

  • 값을 개별 파라미터로 전달받을 수도 있고, 파라미터 두개 이상일 경우 별도 클래스를 사용하는 것이 편리하다.
  • 서비스 파라미터를 결정할 때 표현 영역과 관련된 타입을 사용해서는 안된다. 예) HttpServletRequest, HttpSession 등
    • 응용서비스 단독적으로 테스트 하기 어려워짐
    • 표현영역의 구현이 변경되는 경우 응용서비스 구현도 함께 변경해야 함
    • 표현영역이 담당해야 할 기능이 서비스 영역에 구현될 확률이 높아짐
  • 메서드 결과는 표현영역에서 사용자에게 알맞은 결과를 보여줄 수 있도록 알맞는 타입을 리턴한다.

표현 영역

표현 영역의 역할

표현영역과 응용서비스

  • 사용자가 시스템을 사용할 수 있는 흐름을 제공하고 제어한다.
  • 사용자 요청데이터를 응용 서비스가 요구하는 형식으로 변환하여 전달하고 응용서비스의 결과를 사용자에게 응답할 수 있는 형식으로 변환하여 사용자에게 전달한다.
  • 사용자 세션 관리

값 검증

표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다.

[응용서비스에서 값 검증 처리하기]

  • 사용자 경험이 나빠질 수 있다 -> 사용자는 입력폼에 대한 모든 밸류를 체크해주길 바라지만 응용서비스에서 값검증을 하면 익셉션이 발생한 필드 이후의 필드에 대해서 검사를 안하기 때문
  • 에러 코드를 모아 하나의 익셥션으로 발생시키는 방법이 있다. (errors란 컬렉션 변수에 모든 에러 목록 담아 리턴)

[표현영역에서 값 검증 처리하기]

  • Validator와 같은 스프링이 제공하는 기능을 사용할 수 있다

[응용서비스와 표현영역에서 역할 분리해 값 검증 처리하기]

  • 표현 영역 : 필수 값, 값의 형식, 범위 검증
  • 응용 서비스: 데이터 존재유무와 같은 논리적 오류 검증
  • 역할 분리에 대한 의견차이가 발생 할 수 있다.

권한 검사

[표현영역에서 권한검사하기]

  • 인증된 사용자인지 체크 > 사용자의 인증 정보를 생성하고 인증 여부를 검사하는 Servlet Filter에서 접근 제어 가능
  • 권한에 대해서도 필터를 이용해서 URL별 권한 검사 할 수 있다.
  • 스프링 시큐리티를 이용하면 필터를 사용해 인증 정보를 생성하고 웹 접근을 제어할 수 있다.

[응용 서비스에서 권한검사하기]

  • 메서드 단위로 권한 검사가 필요할 때 스프링 시큐리티 AOP를 활용할 수 있다.
  • @PreAuthorize("hasRole('ADMIN')")

[도메인에서 권한검사하기]

  • 개별 도메인 객체 단위로 권한 검사를 할 때
    • 예)게시글 삭제가 본인과 관리자만 가능한 경우 
    • 스프링 시큐리티 같은 보안 프레임워크를 확장해서 도메인 객체 수준의 권한 검사 기능을 프레임워크에 통합 할 수있다.
    • 프레임워크 확장이 어려운 경우 도메인에 맞는 권한 검사 기능을 직접 구현하는 것이 코드 유지보수에 유리하다.

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

  • 응용 서비스가 사용자 요청기능에 기여하는바가 없다면 생략해도 된다.
  • 예를 들어 (ViewDao과 같은) 조회 전용 기능이 있고, 서비스에서 수행하는 추가적인 로직이 없는 단순 조회 기능의 경우 굳이 서비스를 만들 필요 없이 표현영역에서 바로 조회 전용 기능을 사용해도 문제가 없다.
public class OrderController {
  ...
  @RequestMapping("/myorders")
  public String list(ModelMap model) {
    ...
    List<OrderView> orders = orderViewDao.selectByOrderer(ordererId);
    ...
  }
}

Reference

도메인 주도개발 시작하기, 최범균 저

반응형