Blog

Facade로 답변 생성 로직의 의존성 분리하기

이전 글에서는 기존 코드를 리팩터링했습니다. 회원 조회, 질문 조회, 답변 저장은 메서드 하나가 모두 맡고 있었습니다. 이번에는 그 메서드를 나누고 호출하는 쪽에서는 과정을 간단하게 볼 수 있도록 정리했습니다.

Facade가 감추는 것

Facade는 여러 작업을 하나의 진입점으로 묶어 내부의 복잡한 과정을 감춥니다. 덕분에 호출자는 각 서비스를 어떻게 조합하는지 몰라도 필요한 작업을 요청할 수 있습니다.

서비스 조합을 별도로 두면 각 도메인의 로직과 조합하는 로직이 나뉘어서 따로 살펴보기 쉽습니다. 외부 변경이 호출자에게 직접 전해지는 범위도 줄일 수 있습니다. 그렇다고 Facade에 일을 계속 모으면 그 클래스가 다시 복잡해집니다. 클래스를 하나 더 만든다고 해서 독립성이나 테스트 용이성까지 보장되지는 않습니다.

답변 서비스에 모여 있던 작업

Java
@Service  
public class AnswerService {  
  
	private final AnswerRepository answerRepository;  
	private final MemberService memberService;  
	private final QuestionService questionService;  
	private final AnswerMapper answerMapper;  
	  
	public AnswerService(AnswerRepository answerRepository, MemberService memberService,  
	QuestionService questionService, AnswerMapper answerMapper) {  
		this.answerRepository = answerRepository;  
		this.memberService = memberService;  
		this.questionService = questionService;  
		this.answerMapper = answerMapper;  
	}  
	  
	@Transactional  
	public void create(AnswerRequest request, Long memberId) {  
		Member findedMember = memberService.findById(memberId);  
		Question findedQuestion = questionService.findById(request.questionId());  
		answerRepository.save(answerMapper.answerRequestToEntity(request, findedQuestion, findedMember));  
	}  
}

create() 하나 때문에 AnswerService가 MemberService와 QuestionService를 함께 참조하고 있었습니다. 그래서 두 조회와 엔티티 변환을 Facade로 옮겼습니다.

조회와 조합을 Facade로 옮깁니다

Java
@Service  
public class AnswerService {  
  
private final AnswerRepository answerRepository;  
private final AnswerFacade answerFacade;  
  
public AnswerService(AnswerRepository answerRepository, AnswerFacade answerFacade) {  
	this.answerRepository = answerRepository;  
	this.answerFacade = answerFacade;  
}  
  
	@Transactional  
	public void create(AnswerRequest request, Long memberId) {  
		Answer answer = answerFacade.create(request, memberId);  
		answerRepository.save(answer);  
	}  
}
Java
public interface AnswerFacade {  
  
	Answer create(AnswerRequest request, Long memberId);  
}
Java
@Component  
public class AnswerFacadeImpl implements AnswerFacade {  
  
	private final MemberService memberService;  
	private final QuestionService questionService;  
	private final AnswerMapper answerMapper;  
	  
	public AnswerFacadeImpl(MemberService memberService, QuestionService questionService,  
	AnswerMapper answerMapper) {  
		this.memberService = memberService;  
		this.questionService = questionService;  
		this.answerMapper = answerMapper;  
	}  
	  
	@Override
	@Transactional
	public Answer create(AnswerRequest request, Long memberId) {  
		Member findedMember = memberService.findById(memberId);  
		Question findedQuestion = questionService.findById(request.questionId());  
		return answerMapper.answerRequestToEntity(request, findedQuestion, findedMember);  
	}  
}

이제 AnswerService는 AnswerRepository와 AnswerFacade에만 의존합니다. 다른 도메인의 조회와 AnswerMapper 호출은 AnswerFacadeImpl이 맡습니다. 실제 저장은 여전히 서비스에 남겨 두었습니다.

Facade의 create()에도 @Transactional을 붙였습니다. 기본 전파 방식인 REQUIRED는 기존 트랜잭션이 있으면 거기에 참여합니다. 당시에는 Facade를 다른 곳에서도 호출할 가능성을 고려해 붙였습니다.

남겨 둔 고민

외부 도메인을 조합하는 부분은 분리했습니다. 그런데 AnswerMapper의 위치는 아직 고민입니다. 서비스와 Facade 양쪽에서 사용해도 괜찮을까요?