기존에 있던 코드들을 Facade패턴으로 리팩토링하는 과정에서한 생각들과 이를 통해 상세한 기조를 세운 과정을 기술했습니다.
Service의 Repository 의존성을 낮추는 것이 시작이었지만 개발하는 과정에서 데이터베이스 관점에서 많이 바라본 경향으로 인해 Controller에 선언된 많은 Service들로 가독성이 떨어지고 유지보수와 추가적으로 API를 늘려가는 과정에서 불편함과 개발속도가 저하되는 일들이 점차 늘어갔습니다.
이에 대한 예시 코드입니다.
public class MemberController {
private final MemberService memberService;
private final AHistoryQueryService aHistoryQueryService;
private final bHistoryQueryService BHistoryQueryService;
private final BoardQueryService boardQueryService;
private final CommentQueryService commentQueryService;
{생략된 Service}
}
개발시작 당시에는 Controller 하나당 하나의 Service라고 생각했지만 엔티티로 바라봤을때는 다양한 Service를 호출하는게 맞지않은가 하는 생각에 Service의 Repository 의존성을 분산시킨 구조가 되어버렸다.
도메인 관점에서 봤거나 Service에서 Repository를 호출하는 것에 대한 반대의 의견에 설득하지 못하고 동의한 아쉬움도 남고 이 후 문제제기되었으나 빠르게 로직 구현하다보니 레거시가 되어버렸다고 생각합니다. 이로 인해 분산된 것을 다시 합치고 분리하는 작업으로 인해 시간이 매우 많이 길어졌습니다.
이에 대한 개선한 코드입니다.
public class MemberController {
private final MemberFacade memberFacade;
// private final AuthFacade authFacade;
}
public class MemberFacade {
private final MemberService memberService;
private final AHistoryService aHistoryService;
private final BHistoryService bHistoryService;
private final BoardService BoardService;
private final CommentService CommentService;
}
도메인에 맞는 Facade를 독립적으로 선언하고 Facade 연관된 Service의 의존성을 통합한 형태입니다.
주석처리의 경우, Member의 경우 인증관련(로그인) 도메인 명이 같은 경우 인증 서비스를 호출도 할 수 있으나 이를 분리하는 작업까지 진행하는 것이 의존성을 낮추고 스파게티코드가 되는 것을 방지합니다. 두 개 이상의 Facade호출이 되고있다면 의존성이 높아지는 것을 알고 있어야합니다.
가장 많이 고민하고 시간이 든 주제입니다. MemberFacade에서 MemberRepository를 바로 선언한 경우도 많이 발견했었는데 실제로 구현한 경우 재사용을 못하는데 왜 이렇게 표현했는지 이해하는데 많은 시간이 걸렸습니다.
결론은 선언하지 않는다입니다.
이러한 이유는
기존로직 분리 -> 함수화 -> 선언
순으로 작업이 필요하게 되는데 이 과정을 생략할 수 있기 때문입니다.위의 주제에서 연결된 주제입니다. 처음에는 Facade에서 모든 비즈니스로직을 처리하고 Service에서는 단순 엔티티 접근하는 것으로 분리했으나 비슷한 로직일 경우 빈번하게 똑같이 선언해야 경우가 있었습니다. 예외처리를 포함한 로직이 똑같이 선언이 된다면 Service에도 예외처리가 포함된 함수를 만들어 재사용합니다.
public class AppFacade {
private final AppService appService;
public Object get(Member member, Long appId) {
App app = appService.get(member, appId);
return AppResponse.of(app);
}
public Object update(Member member, Long appId) {
App app = appService.get(member, appId);
appService.update(app);
return AppResponse.of(app);
}
}
public class AppService {
public App get(Member member, Long appId) {
return appRepository.findByAppId(appId).orElseThrow(AppNotFoundException::new);
}
}
엔티티에 대한 모든 수정 이후 마지막에 Reponse를 반환합니다. 예외적으로 QueryDSL로 조회하는 경우에는 Repository에서 반환하는 것을 허용합니다.
기본적으로 Facade에 @Transactional이 선언되어있지만 필요에 따라 Service에서 선언하여 트랜잭션 시점을 관리하여 로직을 구성합니다.
먼저 예시 코드입니다.
public class CrewFacade {
private final AppService appService;
private final MemberService memberService;
private final CrewService crewService;
public Object invite(Member member, Long appId, String memberId) {
return appService.findByIdAndMember(member, appId)
.map(app -> {
memberService.findById(memberId)
.map(invitedMember -> {
crewService.create(app, invitedMember);
return Boolean.TRUE;
})
.orElseThrow(() -> new MemberFoundException(memberId));
return Boolean.FALSE;
})
.orElseThrow(AppNotFoundException::new);
}
}
가독성과 성능을 고려하지않는 그냥 한줄로 만들고싶은 허영심에 가득차서 만들어 본 코드입니다.
해당 패턴을 사용하게 될 경우 대부분의 비즈니스 로직은 Facade에서 처리가 될 것입니다.
하지만 리팩토링 하는 과정에서 Service 함수를 최소로 사용되거나 재사용하지 못하는 경우가 발생하고 이 코드를 처음 본 사람이 이해하는데에 시간이 걸릴것 같다는 생각이 들고 앞서말했듯 가독성이 떨어지는 코드는 협업에 있어서 하면안된다고 생각하기 때문에 해당 코드 패턴은 사용하지 않습니다.
아직 초기 단계라 상세한 기조는 더 개발하는 과정에서 만들어 갈 것이지만 어느 정도 정립이 된 시점이라고 생각합니다. 신규 프로젝트는 금방 적용했으나 그전 프로젝트는 개발이 많이 상태라 서버하나당 3일정도의 시간이 걸렸습니다. 다음 서버를 작업하면할수록 작업량이 줄어들었고 비슷한 함수들을 통일화하는 작업예정입니다.
엄청 고민 많이하고 흔들리지않기 위한 방법을 찾아가는 과정이 고통스러웠지만 재밌었습니다. 꾸준히 부딪히던 부분들을 해결한 방법이라서 오래된 문제를 해결해서 정말 좋았습니다. 기존 프로젝트 또한 레거시가 된다는 것에 엄청난 스트레스가 였지만 작업하는 과정에서 더 상세한 기조를 세우는데 큰 도움이 됐습니다.
글을 자꾸 쓰다보니 어조도 바뀜