본문 바로가기
Architecture/Clean Architecture

[Clean Architecture] 3. 설계원칙 - (3) OCP (개방-폐쇄 원칙)

by 잭피 2021. 9. 12.

개방 폐쇄 원칙(OCP)라는 용어는 1988년 버트란트 마이어가 만들었습니다

소프트웨어 개체는 확장에 열려 있어야 하고, 변경에는 닫혀 있어야 합니다

 

소프트웨어 아키텍처를 공부하는 가장 근본적인 이유가 바로 이 때문입니다

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

아키텍처를 공부하기 시작한 지 얼마 안된 사람들 대다수는 OCP를 클래스와 모듈을 설계할 때 도움되는 원칙이라고 알고 있습니다

 

하지만 아키텍처 컴포넌트 수준에서 OCP를 고려할 때 훨씬 중요한 의미를 가집니다

 

사고 실험

한 예를 통해 보도록 합시다

재무제표를 웹 페이지에 보여주는 시스템이 있습니다

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

 

추가로 흑백 프린터로 출력해 달라고 요청이 왔다고 가정해봅시다

페이지 번호, 머리글, 바닥글, 각 열에는 레이블이 필요하고 음수는 괄호로 감싸야합니다

당연히 새로운 코드를 작성해야 합니다

얼마나 많은 코드를 수정해야 할까요?

 

소프트웨어 아키텍처가 훌륭하다면 변경되는 코드의 양이 가능한 한 최소화될 것입니다

우선 서로 다른 목적으로 변경되는 요소를 적절하고 분리합니다 - (단일 책임 원칙, SRP)

이들 요소 사이의 의존성을 체계화함으로써(의존성 역전 원칙, DIP) 변경량을 최소화할 수 있습니다

 

SRP를 적용하면 데이터 흐름을 아래와 같은 형태로 만들 수 있습니다

 

보고서 생성이 2개의 책임으로 분리되어 있습니다

  1. 보고서용 데이터를 계산하는 책임
  2. 웹이나 종이로 데이터를 표현하는 책임

이처럼 책임을 분리했다면, 두 책임 중 하나에서 변경이 발생하더라도 다른 하나는 변경하지 않을 수 있습니다

이런 목적을 달성하려면 처리 과정을 클래스 단위로 분할하고, 아래와 같이 이중선으로 표시한 컴포넌트 단위로 구분해야 합니다

 

Controller, Interfactor, Database, Presenter, View 컴포넌트로 구분할 수 있습니다

모든 컴포넌트 관계는 단방향으로 이루어집니다

이들 화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려집니다

A 컴포넌트에서 발생한 변경으로부터 B 컴포넌트를 보호하려면 반드시 A 컴포넌트가 B 컴포넌트에 의존해야 합니다

 

Presenter에서 발생한 변경으로부터 Controller를 보호하고자 합니다

그리고 View에서 발생한 변경으로부터 Presenter를 보호하고자 합니다

Interactor는 OCP를 가장 잘 준수할 수 있는 곳에 위치합니다

Database, Controller, Presenter, View에서 발생한 어떤 변경도 Interactor에 영향을 주지 않습니다

 

Interactor가 업무 규칙을 포함하기 때문에 이처럼 특별한 위치를 차지해야합니다

애플리케이션에서 가장 높은 수준의 정책을 포함합니다

가장 중요한 문제는 Interactor가 담당하고 이외의 컴포넌트가 주변적인 문제를 처리합니다

 

Interactor는 가장 높은 수준의 개념이고 최고의 보호를 받습니다

View는 가장 낮은 수준의 개념 중 하나이며, 따라서 거의 보호를 받지 못합니다

[ Interactor > Controller > Presenter > View ]

 

이것이 바로 아키텍처 수준에서 OCP가 동작하는 방식입니다

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

 

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

방향성 제어

FinancialDataGateway 인터페이스는 FinancialReportGenerator와 FinancialDataMapper 사이에 위치하는데, 이 인터페이스가 없었다면, 의존성이 Interactor 컴포넌트에서 바로 Database 컴포넌트로 향하게 됩니다

정보 은닉

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

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

이 인터페이스가 없었다면 Controller는 FinancialEntities에 대해 추이 종속성을 가지게 됩니다

 

추이 종속성을 가진다는 것은 소프트웨어 엔티티는 '자신이 직접 사용하지 않는 요소에는 절대로 의존해서는 안 된다'는 소프트웨어 원칙을 위반하게 됩니다

 

Controller에서 발생한 변경으로부터 Interactor를 보호하는 일의 우선순위가 가장 높지만,

반대로 Interactor에서 발생한 변경로부터 Controller도 보호되기를 바랍니다

이를 위해 Interator 내부를 은닉합니다

결론

OCP는 시스템의 아키텍처를 떠받치는 원동력 중 하나입니다

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

 

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

 

 

이 글은 학습을 위해 “클린 아키텍처" 책 내용을 정리한 글입니다.

저작권 관련 문제가 있다면 바로 삭제하도록 하겠습니다

 

댓글