작성
·
2.7K
4
안녕하세요. 강의가 너무 재미있어 시간가는 줄 모르고 1편, 2편 강의 완강 하고있습니다. 2편, 테스트 관련해서 실습을 하던 중 고민이 생겨서 질문을 올리게 되었습니다.
자바/스프링으로 코딩을 하다가 A Service와 B Service가 의존관계를 갖게 된다면 어떻게 해야할지가 너무 고민이여서요.
만약 2편에 있던 도메인 User, Post에 User의 프로필 이미지 업로드기능, Post의 이미지 업로드기능이 있다고 할 때, File의 정보를 저장할 수 있는 File 도메인이 있어야 한다고 생각합니다.
File에 대응하는 FileService를 만든다고 가정 하였을 때 DB 등에 파일의 정보 데이터를 저장하는 FileRepository, 파일을 업로드 하는 FileUploader를 만든 후 의존관계가 이런형태로 구성 되어야 한다고 생각합니다.
FileService에 saveAndUpload() 라는 메서드가 있고 이 메서드를 공통으로 사용하고 싶을 때 UserServiceFacade, PostServiceFacade와 같은 형태로 퍼사드를 써야할지, UserService, PostService 에 각각 업로드 메서드를 구현을 해야할지 고민이 됩니다.
Facade와 같은 형태로 코드를 작성하게 된다면 User, Post가 아닌 파일이 필요로 하는 도메인이 추가 될 때 마다 Facade를 작성해줘야하는 번거로움이 있을 것 같고,
Service 각각에 업로드 메서드를 구현하자니 반복적인 코드가 들어가는 것이 마음에 조금 걸려서 질문드립니다.
답변 1
6
안녕하세요. 이전에 올라온 문의에 답변을 달다 보니 금새 새로운 문의가 하나 올라와 있었네요. 강의를 좋게 봐주셔서 감사합니다!
우선 문의주신 내용 쭉 읽어봤는데요. 읽으면서 다이어그램을 올려주신 부분까지는 모두 납득이 가는 터라 이해했습니다. 저도 개발을 한다면 이야기해 주신 것처럼 FileRepository, FileUploader를 나눠서 개발할 것 같습니다.
다만 이해가 안 가는 것은 UserService와 PostService가 FileService.saveAndUpload를 호출하면 안 되는 상황인 건가요? 아래와 같이 코드를 만들면 될 것 같은데요.
public class MyFile {
public String path;
public File file;
}
public interface MyFileService {
public void saveAndUpload(MyFile file);
}
public class UserServiceImpl implements UserService{
// ...
@Transactional
public void updateProfileImage(long userId, File file) {
User user = this.userRepository.getById(userId);
user.updateProfileImagePath(file);
this.fileService.saveAndUpload(file);
}
}
위와 같이 작성하면 크게 문제 없을 것 같은 데, 어떤 고민을 하고 있는 것인지 잘 모르겠더라고요. 그래서 질문자님이 질문을 올린 이유에 대해 추측하고 이에 대해 각각 답변을 달고자 합니다.
추측1.
혹시 Facade 패턴을 학습하시고 ‘Service는 Service를 의존해선 안 된다’라는 생각을 하고 계신 게 아닌가 싶습니다. 이는 Facade 레이어를 도입하는 개발자 분들 한테서 주로 보는 현상인데요.
Facade를 도입한다 = 서비스가 타 서비스를 참조해선 안 된다
라는 소리가 아닙니다. 그래서 의존 관계를 지나치게 망가뜨리지 않는다면 마음 편히 서비스 간 참조를 해도 괜찮지 않나라고 생각합니다.
더불어 개인적인 관점에서 Service는 Facade 패턴의 일종입니다. 왜냐하면 애초에 스프링에서도 Service를 Business service facade라고 정의하고 있기 때문입니다. Facade 패턴이 무언가 어렵고 새로운 패턴인 것처럼 보이지만 실상은 지저분한 것을 가려주는 정문을 만드는 것에 불과합니다.
다시 말해 흔히들 스프링에 Facade 패턴을 적용하면서 Facade라는 새로운 컴포넌트를 만들었다고 착각하지만 실은 새로운 유형의 service를 만든 것에 불과하다는 것입니다. 예를 들어 UserServiceFacade를 만든다고 해주셨는데요. 아래와 같이 코드를 짜시겠다는 의미로 이해했습니다. 맞을까요?
public class UserServiceFacade {
// ...
@Transactional
public void update(UserUpdate userUpdate) {
this.userService.update(userUpdate);
this.fileService.saveAndUpload(userUpdate.getProfileImage());
}
}
그런데 이게 이 코드랑 다를게 뭘까요…?
public class UserServiceImpl implements UserService {
// ...
@Transactional
public void update(UserUpdate userUpdate) {
this.update(userUpdate);
this.fileService.saveAndUpload(userUpdate.getProfileImage());
}
private void update(UserUpdate userUpdate) {
// ...
}
}
아니면 또 이런 코드랑은 다를게 뭘까요?
public class UserUpdateService {
// ...
@Transactional
public void update(UserUpdate userUpdate) {
this.userService.update(userUpdate);
this.fileService.saveAndUpload(userUpdate.getProfileImage());
}
}
그러한 이유로 저는 솔직히 Facade 계층의 필요성을 잘 모르겠습니다. 방금 Service = Business service facade 라는 이야기를 해드렸는데요. 그렇다면 UserServiceFacade는 UserBusinesssServiceFacadeFacade라는 의미가 됩니다. 이상하지 않나요...?
서비스가 타 서비스를 호출하는 것은 분명 주의해야 할 일이지만, 그렇다고 너무 경계할 필요는 없다고 생각합니다. (물론 순환 의존성이 생기지 않는 것을 의식하면서 + 책임이 과하게 할당되는 것을 경계하면서요.)
추측2.
Service 각각에 업로드 메서드를 구현한다는 말이 잘 이해가 안 되는데요. FileService의 구현체나 인터페이스 공통화가 덜 된 상황은 아닌 것인지 생각해 보게 됩니다.
그게 아니라 만약 충분히 공통화가 되어있는 상황이라면, UserService에서의 FileService.saveAndUpload 호출과 PostService에서의 FileService.saveAndUpload 호출은 필연적으로 다른 로직일 수밖에 없다는 뜻입니다. 따라서 이는 코드 중복이 아니니 마음 편히 작성해도 된다고 생각합니다.
답변이 조금이라도 도움이 되었으면 합니다. 감사합니다.
늦은 시간 너무 좋은 답변 감사합니다. 추측 1번이 제가 원했던 답변이었던 것 같습니다.
1번에서는 서비스에서 다른 서비스 의존, 2번은 UserService, PostService도 각각 FileRepository, FileUploader의 의존성을 갖고 saveAndUpload를 구현해야 한다는 말 이었습니다.
Service간의 의존에 대해 항상 고민이었고 괜찮을까 라는 생각이 가득했었는데 드디어 풀렸습니다. 감사합니다