6.1 표현 영역과 응용 영역

표현영역은 사용자의 요청을 해석한다. 요청을 받은 표현영역은 URL, 요청 파라미터, 쿠키, 헤더 등을 이용해서 사용자가 실행하고 싶은 기능을 판별하고 그 기능을 제공하는 응용 서비스를 실행한다.

실제 사용자가 원하는 기능을 제공하는 것은 응용 영역에 위치한 서비스다. 사용자가 회원 가입을 요청했다면 실제 그 요청을 위한 기능을 제공하는 주체는 응용 서비스에 위치한다. 응용서비스를 실행한 뒤에 표현 영역은 실행 결과를 사용자에게 알맞은 형식으로 응답한다.

@PostMapping("member/join")
public ModelAndView join(HttpServletRequest request){
	String email = request.getParameter("email");
	String password = request.getParameter("password");

	// 사용자 요청을 응용 서비스에 맞게 변환
	JoinRequest joinReq = new JoinRequest(email, password);
	// 변환한 객체(데이터)를 이용해서 응용 서비스 실행
	JoinService.join(joinReq);
	...
}

6.2 응용 서비스의 역할

응용 서비스는 사용자의 요청을 처리하기 위해 리포지터리에서 도메인 객체를 가져와 사용한다.

응용 서비스는 주로 도메인 객체 간의 흐름을 제어하기 때문에 다음과 같이 단순한 형태를 갖는다.

public Result doSomeFunc(SomeReq req){
		//1. 리포지터리에서 애그리거트를 구한다.
		SomeAgg agg = someAggRepository.findById(req.getId());
		checkNull(agg);

		//2. 애그리거트의 도메인 기능을 실행한다.
		agg.doFunc(req.getValue());

		//3. 결과를 리턴한다.
		return createSuccessResult(agg);
}

응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다. 응용 서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다.

응용서비스는 트랜잭션 처리도 담당한다.

public void blockMembers(String[] blockingIds){
	if(blockingIds == null || blockingIds.length == 0) return;
	List<Member> members = memberRepository.findByIdIn(blockingIds);
	for (Member mem : members){
		mem.block();
	}
}

blockMembers() 메서드가 트랜잭션 범위에서 실행되지 않는다고 가정해 보자. Member 객체의 block() 메서드를 실행해서 상태를 변경했는데 DB에 반영하는 도중 문제가 발생하면 일부 Member만 차단 상태가 되어 데이터 일관성이 깨지게 된다. 이런 상황이 발생하지 않으려면 트랜잭션 범위에서 응용 서비스를 실행해야 한다.

6.2.1 도메인 로직 넣지 않기

도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생한다.

  1. 코드의 응집성이 떨어진다는 것이다.
  2. 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.

위 두 가지 문제점은 결과적으로 코드 변경을 어렵게 만든다. 변경의 용이성이 낮은 소프트웨어는 그 만큼 가치가 하락할 수 밖에 없다. 소프트웨어의 가치를 높이려면 도메인 로직을 도메인 영역에 모아서 코드 중복이 발생하지 않도록 하고 응집도를 높여야 한다.