이 글은 프로그래머스 - 실무 자바 개발을 위한 OOP와 핵심 디자인 패턴 강의를 정리한 내용입니다.
이번 포스팅에서는 의존 역전에 대해서 다뤄보겠습니다. 의존 역전 역시 의존 관계 만큼이나 객체지향 패러다임의 개념을 이해하는데 핵심이 되는 매우 중요한 개념입니다.
의존 역전의 정의는 다음과 같습니다.
고수준 컴포넌트가 저수준 컴포넌트에 의존하지 않도록 의존 관계를 역전 시키는 것
으레 그렇듯 정의를 들었을 때 바로 이해하시는 분이 있으실 수도 있고, 생소한 단어 때문에 더 복잡하게 느끼시는 분들도 있을 겁니다.
😵💫 저수준? 고수준? 컴포넌트...????
정의를 이해하기 위해 단어들의 의미를 하나씩 알아봅시다.
우선 컴포넌트
는 상황에 따라 굉장히 다양한 것들을 지칭할 수 있지만, 해당 포스팅에서는 Class
를 의미한다고 받아들이시면 됩니다.
Spring 프레임워크를 사용해서 웹 애플리케이션을 개발한다고 하면 아마 위 그림같은 패턴을 자주 보았을겁니다.
이들은 각각 하나의 클래스로 작성되는데, Controller
는 브라우저와 같은 클라이언트로 부터 사용자들의 요청을 받아 Service
를 호출합니다.
그리고 Service
는 호출을 받아 해당 웹 애플리케이션이 해야하는 주요 동작을 수행하고, 그 과정에서 DB에 저장된 데이터가 필요하다면 Repository
를 호출합니다.
예시가 너무 추상적인가요? 좀 더 구체적인 예시를 들어보겠습니다.
게시글을 작성하고 조회하는 웹 애플리케이션을 개발한다고 가정하면 다음의 과정을 생각해볼 수 있습니다.
Controller
는 작성하고자 하는 게시글 내용을 클라이언트로 부터 받습니다. 그리고 Service
를 호출합니다.Service
는 Repository
를 호출해서 게시글을 저장합니다.Controller
는 조회할 게시글의 번호를 받아 Service
로 넘기고, Service
는 Repository
를 통해 올바른 게시글 정보를 받아 다시 Controller
로 넘깁니다.다시 원래 그림으로 돌아와서 우리가 알고 싶었던 키워드인 고수준 컴포넌트
와 저수준 컴포넌트
에 대해서 생각해보겠습니다.
아까 게시글을 저장할때는 Repository
를 통해 DB에 저장한다고 하였는데, 요구 사항이 변경되어서 특정 상황에서는 게시글을 DB 대신 파일 서버에 저장한다고 가정해보겠습니다.
변경된 요구사항을 적용하기 위해 Repository
는 그림과 같이 두 개로 나누어질 것입니다.
아마 지금까지 제 포스팅을 보았다면 눈치채셨겠지만, 위 구조는 문제가 있습니다. Service
가 구현 클래스를 의존하고 있기 때문에 DB 혹은 파일 시스템에 저장하는 상황마다 Service
의 코드를 변경해줘야합니다.
이런 문제가 발생한 이유는 다음과 같습니다.
고수준 컴포넌트인
Service
가 저수준 컴포넌트인xxxRepository
에 의존하기 때문입니다.
여기서 중요한 포인트는 왜 각각의 Repository
는 저수준 컴포넌트로 분류되고, Service
는 고수준 컴포넌트로 분류되는지에 대한 이유일겁니다.
각각의 컴포넌트를 고수준 & 저수준으로 분류하는 결정적인 차이는 구체적인 기술에 종속적이냐, 아니냐의 차이입니다.
DatabaseRepository
는 DB라는 구체적인 기술을 사용해서 게시글을 저장하고, FileRepository
는 파일 입출력이라는 구체적인 기술을 사용해서 게시글을 저장합니다.
반면 Service
는 '무언가를 저장한다는 행위'의 구체적인 과정은 알지 못하고, 단지 Controller
의 요청에 따라 게시글을 저장하는 구체적인 작업을 Repository
에 위임하고 있습니다.
따라서 구체적인 기술을 사용하고 있지 않는 Service
는 고수준 컴포넌트, 구체적인 기술을 담당하고 있는 Repository
들은 저수준 컴포넌트라고 분류할 수 있습니다.
현재 구조는 고수준 컴포넌트인 Service
가 저수준 컴포넌트인 xxxRepository
를 직접 의존하고 있기 때문에, 우리가 필요할 때 마다 Service 코드를 변경해서 각각의 구현 Repository 클래스를 수정해줘야 하는 것입니다.
객체지향적인 구조를 고려하지 않고 코드를 작성할 경우, 고수준 컴포넌트가 저수준 컴포넌트를 의존하도록 코드가 작성되는 것은 아주 쉽게 발생할 수 있는 상황입니다.
물론 고수준 컴포넌트가 저수준 컴포넌트를 직접 의존하는게 무조건 잘못된 구조는 아닙니다. 하지만 지금과 같이 상황에 따라 다른 구현 클래스가 필요한 경우는 의존 역전이 필요한 상황입니다.
지금까지 알아본 내용에 따르면 의존 역전을 한다는 것은 다음과 같습니다.
고수준 컴포넌트가 저수준 컴포넌트에 의존하지 않도록 의존 관계를 역전 시키는 것
그럼 이걸 어떻게 할 수 있을까요? 사실 우리는 이미 방법을 알고 있습니다. 바로 중간에 추상화(인터페이스)를 적용하는 것입니다.
Repository Interface는 추상적인 존재이기 때문에 구체적인 기술에 대해서는 모릅니다. 그저 이 인터페이스를 구현하는 클래스들이 구체적인 기술을 사용할 뿐입니다. 따라서 Repository Interface는 고수준 컴포넌트라고 분류할 수 있습니다.
의존 역전
이라는 단어에서 역전이라는 표현은 고수준에서 저수준 방향으로 흐르던 의존성이 반대로 저수준에서 고수준 방향으로 역전되었기 때문에 이 의존성 방향의 반전을 의존 역전이라고 표현합니다.
실제로 Service
는 기존 저수준 컴포넌트들인 두 Repository 클래스를 의존하던 방향에서 고수준 컴포넌트인 Reposiory Interface를 의존하도록 변경되었습니다.
그리고 각각의 저수준 컴포넌트들 역시 고수준 컴포넌트인 Reposiory Interface를 구현함으로써 저수준 -> 고수준 방향의 의존 관계가 되었습니다.
결론부터 얘기하면 Controller
는 저수준 컴포넌트입니다.
Controller는 HTTP
라는 구체적인 기술에 종속적인 클래스입니다. 따라서 HTTP
외에도 키보드를 통한 표준 입력 등 여러가지 방법을 통해 클라이언트로 부터 데이터를 받을 수 있습니다.
만약 HTTP로 받는 Controller에서 키보드로 입력 받는 Controller로 변경하였을 때 Service 코드가 전혀 변경될 필요가 없다면 그건 객체지향적으로 코드를 잘 작성했을 가능성이 높습니다.
지금까지 다룬 클래스들의 수준을 나누면 다음과 같습니다.
여기서 중요한 포인트는 고수준 컴포넌트는 저수준 컴포넌트에 하나도 의존하고 있지 않다는 것입니다. 의존 역전
이 잘 적용된 구조입니다. 핵심은 고수준 컴포넌트에서 의존하고 있는 저수준 컴포넌트를 추상화 하는 것입니다.
객체지향 프로그래밍이 실무에서 자주 발생하는 문제들을 해결하기 위해 등장한 것처럼, 웹 애플리케이션을 개발하면서 발생하는 비슷한 문제들을 해결하기 위한 코드 패턴도 많이 등장하였습니다.
이런 코드 패턴과 의존 방향을 하나의 컨벤션으로 만들기도합니다. 그런 컨벤션 중 하나로 각각의 코드들을 여러개의 계층으로 나누고 각 계층들간의 의존 방향을 제어하는 방법이 있습니다. (계층들은 패키지로 구분)
위 그림과 같이 계층형 구조를 가지는 패턴을 Layered Architecture
라고 부릅니다.
지금까지 다룬 의존 역전에 관한 내용을 정리하면 다음과 같습니다.
의존 역전은 고수준 컴포넌트가 저수준 컴포넌트를 의존하고 있는 구조에서 추상화를 적용하여 의존 방향이 인터페이스로 모이도록 하는 방법으로 달성할 수 있습니다.
이렇게 하면 고수준 컴포넌트에서 저수준 컴포넌트로 향하고 있던 의존 방향이 뒤집혀서 반대로 저수준 컴포넌트에서 고수준 컴포넌트로 의존 방향이 생기는데, 이것을 의존 방향이 역전되었다고 표현합니다.