Project/싹틔움

[싹틔움] 10/28 개발일지 Service 순환참조 해결 고민 : Repository 직접 의존 / 패서드 패턴

한33 2024. 11. 2. 15:47

☘️ [문제 인식]

favoriteDictionaryService와 userService 간에 상호 의존 관계가 발생했습니다.

 

 

 

favoriteDictionaryService는 사용자가 특정 식물도감을 관심등록 했는지 확인하고 필요시 해제하는 기능을 제공하며, 이 과정에서 유저 조회가 필요합니다.

 

반면 userService는 사용자가 관심등록한 식물도감 리스트를 조회하여 사용자 정보와 함께 출력하는 기능을 담당하고 있습니다.

User 테이블에서 직접 FavoriteDictionary 와 연관관계를 지어서 양방향 관계를 설정하기 보다는 User 객체를 생성할 때 FavoriteDictionary 로 부터 List 를 받아서 출력시키기를 바랬습니다.

이로 인해 두 서비스가 서로의 기능에 의존하게 되어 순환 참조 문제가 발생하게 되었습니다. 

 

☘️ [해결 방안]

1. UserService FavoriteDictionaryRepository에 직접적으로 의존하기

UserService가 FavoriteDictionaryRepository에 직접적으로 의존하는 것은 SRP(Single Responsibility Principle, 단일 책임 원칙)를 위반할 수 있기 때문에 바람직하지 않다.

이 원칙에 따르면 UserService는 사용자와 관련된 로직을 담당해야 하며, 즐겨찾기 사전(FavoriteDictionary)에 대한 관심사는 분리하는 것이 좋다.

  1. 관심사의 분리
    UserService는 사용자 정보와 관련된 로직을 처리하는 역할을 맡고 있습니다. 반면, FavoriteDictionaryRepository는 즐겨찾기 사전에 대한 데이터 접근을 담당해야 하므로, UserService가 FavoriteDictionaryRepository에 직접 의존하면 사용자 서비스가 즐겨찾기 사전이라는 다른 관심사까지 다루게 됩니다. 이는 서비스가 여러 책임을 가지게 되어 유지 보수가 어려워질 수 있습니다.
  2. 서비스 간 결합도 증가
    UserService에서 FavoriteDictionaryRepository를 사용하면 두 서비스가 강하게 결합된다. 이는 서로 독립적으로 변경되기 어렵게 만들고, 서비스 구조의 유연성을 떨어뜨리며 코드의 복잡성을 증가시킨다.
  3. 테스트 용이성 저하
    UserService가 즐겨찾기 사전과 관련된 로직을 포함하게 되면, UserService의 테스트 범위가 넓어지게 되고 복잡한 의존성을 모두 모킹해야 하므로 테스트가 어려워진다. 관심사를 분리하면 각 기능에 대해 개별적으로 테스트할 수 있어, 테스트의 단순성과 유지 보수가 더 쉬워진다.
  4. 확장성 문제
    UserService에 즐겨찾기 기능을 추가하면, 사용자 서비스가 필요 이상으로 커지게 되고, 새로운 기능을 추가할 때 의존성이 겹쳐지는 문제가 생길 수 있다. 예를 들어, 즐겨찾기 기능이 복잡해져 별도의 서비스로 분리해야 하는 경우가 생길 수 있는데, 이미 UserService가 관련 로직을 가지고 있다면 이를 분리하기가 어렵다.

2. 패서드 패턴을 사용해 UserService 와 FavoriteDictionaryService 의 상호작용을 중앙화하기

public class UserFacade {
    private final UserService userService;
    private final FavoriteDictionaryService favoriteDictionaryService;

    public UserFacade(UserService userService, FavoriteDictionaryService favoriteDictionaryService) {
        this.userService = userService;
        this.favoriteDictionaryService = favoriteDictionaryService;
    }

💡패서드 패턴이란?

패서드 패턴(Facade Pattern)은 외부에서 복잡한 서브시스템에 접근할 때 단순화된 인터페이스를 제공하는 패턴으로, 여러 개의 서비스나 클래스 간의 복잡한 상호작용을 감추고 간단한 진입점을 제공하는 역할을 한다. 이 패턴은 특히 순환 참조 문제를 해결하는 데 도움을 줄 수 있으며, 다음과 같은 상황에서 유용하게 사용할 수 있다.

💡패서드 패턴의 장점

  1. 순환 참조 해결: UserService와 FavoriteDictionaryService가 직접 참조하지 않으므로 순환 참조 문제가 해결된다.
  2. 복잡성 감소: UserFacade ( 새로 생성한 클래스 )를 통해 간단한 인터페이스를 제공하여, 서비스 간의 복잡한 로직이 패서드로 감춰진다.
  3. 유연성 증가: UserFacade에서 기능을 변경하거나 추가할 때, UserService나 FavoriteDictionaryService의 내부 로직을 건드리지 않고 조합하여 사용할 수 있다.

💡패서드 패턴의 단점

1. 확장성 제약

  • 패서드 패턴은 여러 클래스를 통합하여 단순화된 인터페이스를 제공하지만, 패서드 클래스가 여러 클래스의 기능을 감싸다 보면 기능이 복잡해지기 쉽다.
  • 새로운 기능이 추가될 때마다 패서드에 해당 메서드를 추가해야 하므로, 패서드 클래스가 지나치게 커지거나 변경에 취약해질 수 있다. 이로 인해 패서드 클래스를 지속적으로 확장해야 하는 상황에서는 패턴의 효과가 떨어질 수 있다.

2. 과도한 책임 집중

  • 패서드가 여러 서비스와 서브시스템의 기능을 모두 포함하게 되면, 결국 패서드 클래스가 하나의 거대한 인터페이스가 되어 비대해질 위험이 있다.
  • 책임이 과도하게 집중되면 단일 책임 원칙(Single Responsibility Principle)이 위반되기 쉬워, 코드 유지보수성이 떨어질 수 있다.

3. 성능 저하 가능성

  • 패서드가 모든 작업을 조정하기 때문에, 실제 사용하지 않는 기능까지 포함될 경우 오버헤드가 생길 수 있습니다.
  • 특히, 불필요한 종속성을 패서드 클래스가 모두 의존하게 되면 메모리 사용량이 늘어나고 성능 저하로 이어질 수 있다. 모든 종속성을 패서드 클래스에 주입하는 것은 필요 이상으로 복잡성을 더할 수 있다.

4. 테스트 복잡성 증가

  • 패서드 패턴은 여러 클래스를 감싸기 때문에 단일 패서드 클래스를 테스트할 때 다양한 종속성을 함께 고려해야 한다.
  • 이로 인해 단위 테스트보다는 통합 테스트를 작성하는 경우가 늘어나며, 테스트 준비가 까다로워질 수 있다. 단일 책임이 분리된 개별 서비스보다 테스트가 복잡하고 시간이 오래 걸릴 수 있다.

5. 유연성 감소

  • 패서드 패턴을 사용하면 서브시스템의 내부 구현에 대한 통제가 제한될 수 있다. 예를 들어, 외부에서 서브시스템의 세부 기능에 접근해야 할 경우 패서드 패턴이 오히려 유연성을 떨어뜨릴 수 있다.
  • 패서드가 제공하지 않는 특정 서브시스템의 기능을 사용하려면 패턴을 우회하거나 패서드 클래스를 수정해야 하므로, 구조가 복잡해질 수 있다.

6. 종속성 문제

  • 패서드 패턴을 도입할 때는 서브시스템 간의 의존성을 적절하게 설계하는 것이 중요하다. 종속성을 잘못 설정하거나 패서드에 너무 많은 역할을 부여하면, 다른 서비스나 패키지들에 의존하게 되어 시스템 전체가 영향을 받을 수 있다.

결론

지금 상황에서는 단순히 서로의 데이터 하나만 가져오기 위해서 서로 상호의존해 순환참조가 일어났기 때문에 패서드 패턴까지 적용하기엔 오히려 과한 비용이 발생할 수 있다.  어떤 방법이든 단점은 존재하기 때문에 지금 같은 상황에서는 그냥 FavoriteDictionaryRepository 를 직접적으로 의존하는 방법을 선택했고, 패서드 패턴에 대해서도 알아봤으니 나중에 더 많은 서비스 로직들이 상호 의존하는 상황을 피할 수 없을 때 사용해보려고 한다.

하지만 가장 중요한 것은 로직을 짤 때부터 구상을 잘 해서 서로 순환참조하는 일이 없도록 하는 것이 중요해보인다.