본문으로 바로가기
728x90
반응형

목차

    개요

    : 순환참조란 ?

    • 두 개 이상의 객체나 컴포넌트가 서로를 참조함으로써 의존관계에 사이클이 생기는 상황을 말한다.
      • 예를 들어 객체 A가 B 객체를 참조하고, 객체 B가 다시 객체 A를 참조하는 양방향 참조는 대표적인 순환참조의 예이다.
    • 이러한 순환 참조는 소프트웨어 설계에서 자주 볼 수 있는 대표적인 안티패턴 중 하나이다.

     

    : ❗JPA의 양방향 매핑은 순환 참조이다.

    • 양방향 매핑이라고 부른다고 해서 순환 참조가 아닌 것은 아니다. 양방향 매핑은 순환 참조 사례 중 하나이다.

     

    : 순환 참조가 발생한다는 것은 서로에게 강하게 의존한다는 것

    • 사실상 하나의 컴포넌트라는 의미이며 책임이 제대로 구분되어 있지 않다는 의미이다. 따라서 순환 참조가 있는 컴포넌트는 SOLID하지도 않다.

     

    5.1 순환 참조의 문제점

    5.1.1 무한 루프

    : 순환 참조가 있다는 것은 시스템에 무한루프가 발생할 수 있다는 말이다.

    • 순환 참조로 인한 무한 루프는 큰 문제일까 ?
      • 무한 루프는 단순히 개발자의 실수로 만들어지는 것은 아니다.
    • 순환 참조가 있는 객체를 직렬화하려 하면 프로그램은 콜 스택이 한계에 다다라 StackOverflow 에러가 발생한다.
      • Jackson과 같은 라이브러리는 이러한 상황을 대비해 @JsonIdentityInfo 같은 애너테이션을 마련해둠
        • 그러나 데이터 구조가 눈에 들어오지 않아 탐탁치 않으며, 직렬화 결과가 불만족스럽고 문제를 억지로 해결했다는 느낌을 지울 수 없다.
    • 무한 루프는 개발자가 메서드 호출 과정을 신경 쓴다고 해결할 수 있는게 문제가 아니다.
      • 더불어 직렬화/역직렬화 상황에서만 발생하는 것도 아니다.
      • 소프트웨어는 복잡계이므로 언제 어디서 어떻게 부작용이 발생할지 모른다. 따라서 순환 참조 인해 발생할 잠재적 위험을 안고 갈 이유가 없다.
    • 해결방법
      • 순환 참조를 만들지 않으면 된다.

     

    5.1.2 시스템 복잡도

    : 순환 참조는 시스템의 복잡도를 높인다.

    • 이것은 의존성 전이 확장 문제와도 비슷한 맥락이다. 시스템 복잡도가 높아진다는 문제는 의존성 전이 확장보다는 조금 더 실무적인 문제다.
    • 시스템의 복잡도가 높아진다는 것은 단순히 코드의 복잡도가 증가한다는 것만 의미하는 것이 아니라 이러한 개발자가 겪는 논리적 혼란도 포함된다.
      • 어떤 요구사항이 있을 때 무의미하며 불필요한 방법으로 구현할 수 있다는 사실은 개발할 때 확실한 노이즈이다.
      • 부적절한 구현 방법으로 인해 더 나은 구현 방법이 있는데도 개발자들이 자꾸 잘못된 방법으로 구현하게 만든다.
    • 의미상으로 그렇게 되어서는 안되는 코드는 컴파일 타임에 아예 만들어지지 않게 해야한다.

     

    : 순환 참조가 있으면 어떤 객체에 접근할 수 있는 경로가 너무 많아진다.

    • “접근 경로가 많다”라는 말은 소프트웨어 설계에서는 그렇게 좋은 말은 아니다.
      • “경로가 많다”라는 것은 의존관계가 복잡하게 얽혀 있다는 의미이기 떄문이다.
    • 가능한 한 도메인 모델들에 단일 진입접을 만들어서 필요한 객체가 있을 때 단방향으로 접근하도록 만드는 것이 좋다.

     

    5.2 순환 참조를 해결하는 방법

    5.2.1 불필요한 참조 제거

    : 불필요한 참조를 제거한다는 것은 양방향 참조가 꼭 필요한지 재고해 본다는 의미이다.

    • 꼭 필요하지 않은 참조를 제거하거나 필요에 따라 관계를 표현하긴 해야 한다면 한쪽이 다른 한쪽의 식별자를 갖고 있게 해서 간접 참조 형태로 관계를 바꾸자.

     

    5.2.2 간접 참조 활용

    : 순환 참조를 제거하는데 간접 참조를 활용할 수도 있다.

    • 참조 객체의 식별값을 이용해 참조하도록 바꾼다. 따라서 직접 참조가 사라지므로 불필요한 참조를 제거한다. 첫 번째 방법과도 유사하다.

    : 💡한방 쿼리보다 단순 쿼리

    • SQL 쿼리가 여러번 발생할 수 있다.
      • 간접 참조를 사용하면 SQL 쿼리 몇 줄이 더 추가될 수 있다. 그러나 짧은 쿼리가 몇 줄 추가되는 것은 생각보다 큰 문제가 되지 않는다.
      • 단순함은 굉장한 무기이다. 단순해야 가독성과 유지보수성이 높아진다. 단순함을 전제로 시스템을 최적화해야한다. 그러는 편이 최적화에 훨씬 더 유리하며 버그도 적게 만들 수 있다.

     

    5.2.3 공통 컴포넌트 분리

    : 서비스 같은 컴포넌트에 순홤 참조가 있고, 그것이 각 컴포넌트의 설정상 필수적이라면 어떻게 해결할까?

    • 간단하고 효과적인 방법으로는 공통 컴포넌트를 분리하는 방법이 있다.
      • 즉, 양쪽 서비스에 있던 공통 기능을 하나의 컴포넌트로 분리하는 것이다. 그러고 나서 양쪽 서비스가 공통 컴포넌트에 의존하도록 바꾸면 순환 참조가 없어진다.
      • 이 방법의 또 다른 장점은 공통 기능을 분리하는 과정에서 책임 분배가 적절하게 재조정된다는 것이다.
        • 컴포넌트의 기능적 분리는 결과적으로 과하게 부여됐던 책임을 분산하며, 그 결과 기능적 응집도를 높이는 효과를 가져온다.
        • 이 과정을 통해 전체 시스템 설계가 단일 책임 원칙에 더욱 부합하는 설계로 진화한다. 각 컴포넌트의 역할과 책임이 명확히 구분되는 것

     

    5.2.4 이벤트 기반 시스템 사용

    : 서비스를 공통 컴포넌트로도 분리할 수 없다면 이벤트 기반 프로그래밍을 시스템에 적용할 수 있다.

    • 이 구조에서 서비스는 더이 상 서로를 상호 참조하지 않는다. 대신 이벤트와 이벤트 큐에 의존한다.
      • 이벤트와 이벤트 큐가 인터페이스이자 곧 메시지가 되는 것이다.
      • 이벤트 기반 시스템은 객체 간의 통신을 이벤트로 이뤄지게 해서 결합을 느슨하게 만들어 순환 참조를 피할 수 있게 도와준다.
      • 이벤트 기반 시스템은 컴포넌트들의 상호 의존성을 끊어내면서도 시스템 설계를 단순하게 만들어 준다.

     

    5.3 양방향 매핑

    : 순환 참조는 어떻게 해서든 없애는 것이 좋으며, 대부분 없앨 수 있다.

    • 순환 참조를 사용하는데는 정말 신중에 신중을 기해야 하며, 같은 맥락으로 양방향 매핑도 사용할 때 신중에 신중을 기해야한다.

     

    : 양방향 매핑은 왜 존재하는 걸까?, 양방향 매핑을 받아들일 때는 다음과 같이 받아들이는 것이 좋다.

    • 양방향 매핑은 도메인 설계를 하다가 “어쩔 수 없이” 나오는 순환 참조 문제에 사용하는 것이 바람직하다.
      • JPA는 수단일 뿐이며, 양방향 매핑을 사용하지 않아도 얼마든지 개발이 가능하다.
      • 우리는 순환 참조가 없는 도메인 먼저 구성해야 한다. 그 다음에 JPA를 연동하는 방식으로 개발해야한다. JPA는 애플리케이션의 핵심이 아니다.

     

    5.4 상위 수준의 순환 참조

    : 순환 참조는 객체 뿐만 아니라 패키지나 시스템 수준에서도 발생할 수 있는 문제이다.

    • 이러한 순환 참조 문제가 패키지나 시스템 수준에서 발생한다면 이는 객체 간 순환 참조보다 더 큰 문제를 야기할 수 있다.
    • 패키지나 모듈, 시스템에서 발생하는 순환참조를 경계하고 독립된 무언가를 만들 수 있어야 한다.
    • 패키지나 시스템, 모듈 수준에서 순환 참조가 발생하면 분리와 유연성이 제한된다.
      • 따라서 개발자는 클래스뿐만 아니라 이러한 상위 수준에서 발생하는 순환 참조를 방지하기 위해 주의해야한다.

     

    : 순환 참조는 명백한 안티패턴이다.

    • 순환 참조를 이용해 개발하는 것은 일시적으로 편리할 수는 있지만 클린 코드 관점에서 다양한 문제를 만든다. 순환 참조 자체를 만들지 않아서 순환 참조로 발생할 수 있는 문제를 미연에 차단해야한다.
    • 그렇게 하는 것이 시스템의 독립성과 유지보수성, 확장성을 높일 수 있는 길이다.
    728x90
    반응형