032. (Clean Architecture) 8. OCP - 개방 폐쇄 원칙

8. OCP - 개방 폐쇄 원칙

이번엔 SOLID의 O에 해당하는 개방 폐쇄 원칙(Open-closed principle) 이다.

개방 폐쇄 원칙이라는 용어의 정의는 다음과 같다.

소프트웨어 개체(artifact)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

다시 말해 소프트웨어 개체의 행위는 확장할 수 있어야 하지만, 이때 개체를 변경해서는 안된다는 원칙이다.

소프트웨어 아키텍처를 공부하는 가장 근본적인 이유가 바로 이 땜누이다.

만약 요구사항을 살짝 확장하는 데 소프트웨어를 엄청나게 수정해야 한다면, 그 소프트웨어 시스템을 설계한 아키텍트는 엄청난 실패를 저지른 것이다.

소프트웨어 설계를 공부하기 시작한지 얼마 안 된 사람들 대다수는 개방 폐쇄 원칙을 클래스와 모듈을 설계할 때 도움이 되는 원칙이라고 알고 있다.

하지만 아키텍처 컴포넌트 수준에서 개방 폐쇄 원칙을 고려할땐 훨씬 중요한 의미를 가지게 된다.

8.1. 사고 실험(thought experiment)

지금부터 재무제표를 웹 페이지로 보여주는 시스템이 있다고 생각해보자.

웹 페이지에 표시되는 데이터를 스크롤할 수 있으며, 음수는 빨간색으로 출력된다.

이 상황에서 이해관계자가 동일한 정보를 보고서로 변환해 흑백 프린터로 출력해달라고 요청했다고 해보자.

이 보고서에는 페이지 번호가 제대로 매겨져있어야하고, 페이지마다 적절한 머리글과 바닥글이 있어야하며

표의 각 열에는 레이블이 존재해야하고, 음수는 괄호로 감싸야한다는 게 요구사항이다.

요구사항이 들어왔으니 변경은 필연적으로 발생하게 될 것이다.

이때 기존 코드를 얼마나 많이 수정해야 할까?

아키텍처가 잘 갖추어져있따는 전제하에 변경되는 코드의 양은 최소화 될 것이다.

서로 다른 목적으로 변경되는 요소를 단일 책임 원칙에 따라 적절하게 분리하고, 의존성 역전 원칙에 따라 이들 요소 사이의 의존성을 체계화하면 변경량을 최소화할 수 있다.

참고 의존성 역전 원칙은 (Clean Architecture) 11. DIP - 의존성 역전 원칙 포스팅에서 다루고 있다.

먼저 단일 책임 원칙을 적용하면 데이터 흐름을 아래와 같은 형태로 표현할 수 있다.

Applying the SRP

재무 데이터를 검사한 후, 보고서용 데이터를 생성한 뒤, 필요에 따라 두 가지 보고서 생성 절차 중 하나를 거쳐 적절한 포맷으로 변환하는 순서이다.

여기서 가장 중요한 사실은 보고서의 생성이 두 개의 책임으로 분리된다는 사실이다.

하나는 보고서용 데이터를 계산하는 책임이며, 나머지 하나는 이 데이터를 웹 혹은 종이의 형태로 표현하는 책임이다.

이처럼 책임을 분리했다면, 두 책임 중 하나에서 변경이 발생하더라도 다른 하나는 변경되지않도록 소스 코드간의 의존성도 확실히 조직홛해두어야 한다.

또한 새로 조직화한 구조에서는 행위가 확장될 때 변경이 발생하지 않음을 보장해야 한다.

이러한 목적을 달성하려면 처리 과정을 클래스 단위로 분할하고, 이들 클래스를 아래와 같이 컴포넌트 단위로 구분해야 한다.

Partitioning the processes into classes and separating the classes into components

위의 그림을 보면 컨트롤러, 인터랙터, 데이터베이스, 프레젠터, 뷰 등 컴포넌트 기준으로 분리된 것을 확인할 수 있다.

여기서 주목할 점은 모든 의존성이 소스 코드 의존성을 나타낸다는 사실이다.

따라서 FinancialDataMapper는 구현 관계를 통해 FinancialDataGateway를 알고 있지만, FinancialDataGatewayFinancialDataMapper에 대해서 아무것도 알지 못한다.

또 하나 주목할 점은 모든 컴포넌트 관계가 단방향으로 이루어진다는 것이다.

이것이 바로 아키텍처 수준에서 개방 폐쇄 원칙이 동작하는 방식이다.

아키텍트는 기능이 어떻게, 왜, 언제 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층 구조로 조직화한다.

컴포넌트 계층 구조를 이와 같이 조직화하면 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있다.

8.2. 방향성 제어

위의 클래스 다이어그램이 다소 복잡하게 느껴질 수 있다.

방향성만 자세히 살펴보자면

FinancialDataGateway 인터페이스는 FinancialReportGeneratorFinancialDataMapper 사이에 위치하는데,

이는 의존성을 역전시키기 위해서이다.

FinancialDataGateway 인터페이스가 없었다면 의존성이 인터랙터 컴포넌트에서 데이터베이스 컴포넌트로 바로 향하게 될 것이다.

FinancialReportPresenter 인터페이스와 두 개의 뷰 인터페이스도 동일하게 의존성 역전이 목적이다.

8.3. 정보 은닉

FinancialReportRequester 인터페이스는 방향성 제어와는 다른 목적을 가진다.

이 인터페이스는 FinancialReportController가 인터랙터 내부에 대해 너무 많이 알지 못하도록 막기 위해서 존재한다.

만약 이 인터페이스가 없다면 컨트롤러는 FinancialEntities에 대해 추이 종속성을 가지게 된다.

추이 종속성은 소프트웨어 엔티티의 “자신이 직접 사용하지 않는 요소에서는 절대로 의존해서는 안된다”라는 원칙을 위반하는 현상이앋.

8.4. 결론

개방 폐쇄 원칙은 시스템의 아키텍처를 떠받치는 원동력 중 하나다.

이 원칙의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있다.

이 목표를 달성하려면 시스템을 컴포넌트 단위로 분리하고, 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층 구조가 만들어지도록 해야한다.