반응형

 

클린 아키텍처의 목표


클린 아키텍처에서 말하는 소프트웨어 아키텍처의 목표는 다음과 같습니다.


'필요한 시스템을 만들고 유지보수하는 데 투입되는 인력을 최소화하는 데 있다.'

인력을 최소화한다는 것은 비용을 최소화한다는 의미이고 이것은 경제적인 관점에서 바라본 목표입니다. 즉, 클린 아키텍처가 지향하는 바는 소프트웨어의 개발과 유지보수의 비용을 최소화하는 것입니다. 따라서 클린 아키텍처는 개발자의 허영심을 채우기 위한 것이 아닙니다. 이것은 손익을 계산하는 경영자가 관심을 가져야 할 내용입니다. 우리 개발자들은 클린 아키텍처를 공부하여 관리자에게 이것의 경제적인 이점을 어필하여 회사의 성장과 개발 역량의 향상을 모두 쟁취하는 이상적인 길을 걸어갈 수 있습니다. 마지막으로 책에서 나온 다음의 문구로 클린 아키텍처의 목표를 다시 한번 정의해봅니다.

 

빨리 가는 유일한 방법은 제대로 가는 것이다.

 

 

 

동작하는 소프트웨어 vs 변경하기 쉬운 소프트웨어

 

후자가 더 중요하다.

완벽하게 동작하지만 수정이 불가능한 프로그램은 요구사항이 변경되면 동작하지 않게 됩니다.
반면 동작은 하지 않지만 변경이 쉬운 프로그램은 돌아가게 만들 수 있고 변경사항이 발생해도 동작하도록 유지보수할 수 있습니다.

 

이것은 너무 극과 극을 비교하여 극단적인 것처럼 보이지만,  원래 극과 극을 비교해야 뭐가 더 중요한지 단순하고 명쾌하게 판단할 수 있습니다.


위에서 말한 것과 같은 변경이 완전히 불가능한 프로그램이란 사실 존재하지 않지만, 수정이 현실적으로 불가능한 시스템은 존재합니다. 변경에 드는 비용이 변경으로 창출되는 수익을 초과하는 경우입니다. 다시 말하지만 다소 경제적인 관점에서 바라볼 필요가 있습니다. 종합하자면 이 문제는 다음과 같이 바꿔쓸 수 있습니다.

행위는 긴급하다 vs 아키텍처는 중요하다

 

상황에 따라 긴급한 것을 먼저 처리해야 할 때도 있지만, 그럴 때조차도 중요한 것에 대해 늘 생각하고 있어야 합니다.

 

 

 

OOP(객체 지향 프로그래밍) vs FP(함수형 프로그래밍)

 

현대 소프트웨어 개발에서는 이 둘을 적절히 섞어 쓰고 있습니다. 사실은 둘 중 하나만 쓰고 나머지 하나를 배제하는 게 훨씬 어렵습니다. 왜냐하면 이것은 2차원 평면좌표에서 x축, y축 위에 점을 정확히 찍는 것과 같기 때문입니다. 어떤 언어를 사용하던 보통은 둘을 자연스레 같이 사용하게 됩니다. 여러 프레임워크, 라이브러리를 사용하면 더욱 그러하게 됩니다.

 

둘의 철학은 각자 장점이 있습니다.

OOP는 다형성으로 의존성 역전을 만듭니다. 이것으로 배포 독립성, 개발 독립성을 가질 수 있습니다. 이것은 관심사의 분리, 도메인 주도 설계, 마이크로 서비스와 같은 설계 방법들로 확장할 수 있습니다.

FP는 변수는 변경되지 않습니다. 가변 변수가 일으킬 수 있는 모든 문제들을 최소화할 수 있습니다. 또한 FP는 유닛 테스트, 클린 코드에 상당한 이점을 가져옵니다.

 

 

 

 

 

 

SOLID 원칙 지키기


클린 아키텍처에서는 다음과 같은 비유를 사용하고 있습니다.


- 좋은 벽돌: 클린 코드
- 좋은 방: 클린 중간 크기의 모듈
- 좋은 빌딩: 클린 아키텍처

벽돌이 가장 작은 단위의 코드라고 한다면 방은 컴포넌트보다는 작은 중간 수준의 모듈을 의미합니다.

좋은 벽돌들로 좋은 방을 만들기 위해 SOLID를 반드시 지켜야 한다고 합니다. 그러면 중간 수준의 소프트웨어는 다음과 같은 목적을 달성할 수 있습니다.

 

- 변경에 유연하다

- 이해하기 쉽다
- 많은 소프트웨어 시스템에 사용될 수 있는 컴포넌트의 기반이 된다.

 

 

 

SOLID 원칙

 

1. SRP: 단일 책임 원칙

 

각 소프트웨어 모듈은 변경의 이유가 하나, 단 하나여야만 한다.

변경의 이유란 이들 사용자와 이해관계자 집단을 가리킵니다. 이들을 액터라고 부릅니다. 따라서 SRP는 다음과 같이 바꿔쓸 수 있습니다.


하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다.

단일 액터를 책임지는 코드를 함께 묶어주는 힘이 바로 응집성입니다.

 

 

위반 징후

 

- 우발적 중복이 발생한다.
- 서로 다른 개발자가 다른 액터에 대한 개발 변경사항을 수정하기 위해 수정을 하고 병합할 때 서로 충돌이 발생한다.

 

 

실천방법

- 서로 다른 액터를 뒷받침하는 코드를 서로 분리하기
- 데이터와 메서드를 분리하기
- 서로 다른 액터들을 서로 다른 클래스로 만들기
- 이러면 여러 클래스를 인스턴스화하고 추적해야 한다는 단점이 있다.
- 이 단점을 극복하는 기법으로 퍼사드 패턴이 있다.

 

 

2. OCP: 개방-폐쇄 원칙

 

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


기존 코드를 수정하기보다는 반드시 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야만 소프트웨어 시스템을 쉽게 변경할 수 있습니다. 소프트웨어 아키텍처가 훌륭하다면 요구사항을 확장할 때 변경되는 코드의 양은 최소화될 것입니다. 이상적인 변경량은 0입니다. OCP의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있습니다. 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 만들어지도록 해야 합니다. 이를 이해하기 위해 밑에서는 DIP에 대해 알아볼 것입니다.

 

 

위반 징후


- 요구사항을 살짝 확장하는 데 소프트웨어를 엄청나게 수정해야 한다

 

 

실천방법

 

- SRP와 DIP를 지킴으로써 요구사항 변경 시 코드 변경량을 최소화할 수 있다.
- SRP 적용: 책임 분리를 추상화 수준으로 그려본다.
- DIP 적용: 각 컴포넌트의 계층 구조를 만든다.

- 보호의 계층 구조는 '수준'이라는 개념을 바탕으로 생성된다.
- 가장 상위 컴포넌트(가장 높은 수준의 개념)는 OCP를 가장 잘 준수한다.

- 가장 하위 컴포넌트(가장 낮은 수준의 개념)는 OCP를 가장 준수하지 못한다.

 

 

3. LSP: 리스코프 치환 원칙


서브 타입에 관한 원칙이다. 상호 대체 가능한 구성요소를 이용해 소프트웨어 시스템을 만들 수 있으려면, 이들 구성요소는 반드시 서로 치환 가능해야 한다.

치환 원칙

S 타입의 객체 o1 각각에 대응하는 T 타입 객체 o2가 있고, T 타입을 이용해서 정의한 모든 프로그램 P에서 o2의 자리에 o1을 치환하더라도 P의 행위가 변하지 않는다면, S는 T의 하위 타입이다.

 

 

치환 가능성을 조금이라도 위배하면 시스템 아키텍처가 오염되어 상당량의 별도 메커니즘을 추가해야 할 수 있습니다.

 

 

실천방법


- 상속 사용
- 잘 정의된 인터페이스와 그 인터페이스의 구현체끼리 상호 치환 가능하게 하기

 

 

4. ISP: 인터페이스 분리 원칙


소프트웨어 설계자는 사용하지 않은 것에 의존하지 않아야 한다.

일반적으로, 필요 이상으로 많은 걸 포함하는 모듈에 의존하는 것은 해로운 일입니다. 소스 코드 의존성의 경우 이는 분명한 사실인데, 불필요한 재컴파일과 재배포를 강제하기 때문입니다. 하지만 더 고수준인 아키텍처 수준에서도 마찬가지 상황이 발생합니다.

 

 

실천방법

 

- 부피가 큰 모듈을 전부 임포트하지 않고 필요한 것만 임포트하기

 

 

5. DIP: 의존성 역전 원칙

 

고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안 된다. 대신 세부사항이 정책에 의존해야 한다.

우리가 의존하지 않도록 피하고자 하는 것은 바로 변동성이 큰 구체적인 요소입니다. 이 구체적인 요소는 자주 변경될 수밖에 없는 모듈들입니다. 예를 들어 인터페이스는 구현체보다 변동성이 낮습니다. 따라서 구현체가 인터페이스에 의존하고 반대는 일어나지 않도록 해야 합니다.

 

 

실천방법


- 변동성이 큰 구체 클래스를 참조하지 말기
- 변동성이 큰 구체 클래스로부터 파생하지 말기
- 구체 함수를 오버라이드 하지 말기
- 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말기
- 추상 팩토리 패턴 사용하기

 

 

 

컴포넌트 수준에서 적용하는 SOLID원칙

 

SOLID는 컴포넌트 수준에서도 적용될 수 있습니다. 여기서 컴포넌트시스템의 구성 요소로 배포할 수 있는 가장 작은 단위를 의미합니다.

 

 

다음은 컴포넌트 응집도와 관련된 세 가지 법칙입니다.

 

1. REP: 재사용/릴리스 등가 원칙

 

재사용 단위는 릴리스 단위와 같습니다. 컴포넌트를 구성하는 모든 모듈은 서로 공유하는 중요한 테마나 목적이 있어야 합니다.

2. CCP: 공통 폐쇄 원칙

 

동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어야 합니다. 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리해야 합니다. 이것은 단일 책임 원칙의 컴포넌트 버전입니다. SRP는 단일 클래스는 변경의 이유가 여러 개 있어서는 안 된다고 말하듯이, CCP는 단일 컴포넌트는 변경의 이유가 여러 개 있어서는 안 된다고 말하는 것입니다. 개방 폐쇄 원칙과도 밀접하게 관련되어 있습니다. OCP의 폐쇄와 CCP의 폐쇄는 같은 뜻입니다.

3. CRP: 공통 재사용 원칙

 

컴포넌트 사용자들을 필요하지 않은 것에 의존하게 강요하지 말아야 합니다. 인터페이스 분리 원칙의 컴포넌트 버전입니다. ISP는 사용하지 않은 메서드가 있는 클래스에 의존하지 말라고 조언합니다. 마찬가지로 CRP는 사용하지 않는 클래스를 가진 컴포넌트에 의존하지 말라고 조언합니다.

 

 

다음은 컴포넌트 사이의 관계와 관련된 세 가지 법칙입니다.

 

1. ADP: 의존성 비순환 원칙


컴포넌트 의존성 그래프에 순환이 있어서는 안 됩니다. 순환 문제가 있으면 컴포넌트 빌드 순서를 정할 수가 없고, 릴리즈가 어려워지고, 테스트가 어려워집니다. 순환을 끊기 위해 의존성 역전 원칙을 적용합니다.
컴포넌트 의존성 다이어그램은 애플리케이션의 빌드 가능성과 유지보수성을 보여주는 지도입니다.

2. SDP: 안정된 의존성 원칙

 

안정성의 방향으로(더 안정된 쪽에) 의존해야 합니다.

 

3. SAP: 안정된 추상화 원칙

 

컴포넌트는 안정된 정도만큼만 추상화되어야 합니다. 가장 높은 단계의 컴포넌트의 추상화 정도가 가장 큽니다. 가장 낮은 단계의 컴포넌트는 추상화 정도가 가장 작습니다.

 

 

 

 

 

 

좋은 아키텍처가 되는 방법

 

좋은 아키텍처가 되기 위해서 실천해야 하는 것들입니다.

 

1. 앞서 살펴본 SOLID원칙을 지켜 코드를 짜고 설계해야 합니다.

2. 세부사항에 대한 결정을 가능한 한 오랫동안 미룰 수 있는 방향으로 정책을 설계합니다. 세부사항은 정책보다 낮은 단계에 있기 때문입니다.
3. 정책은 세부사항에 관한 어떠한 지식도 갖지 못하게 되며 어떤 경우에도 세부사항에 의존하지 않게 합니다.

4. 개발 초기에는 선택사항을 열어둡니다. 고수준의 정책은 이런 것들을 신경 써서는 안 됩니다. (데이터베이스 종류, 웹 서버, REST, 프레임워크) 

5. 시스템이 다음과 같은 특징을 갖도록 합니다.

 

- 프레임워크 독립성: 아키텍처는 프레임워크의 존재 여부에 의존하지 않는다. 프레임워크를 도구로 사용할 수 있다.
- 테스트 용이성: 업무 규칙은 UI, 데이터베이스, 웹 서버 또는 여타 외부 요소가 없이도 테스트할 수 있다.
- UI 독립성: 시스템의 나머지 부분을 변경하지 않고도 UI를 쉽게 변경할 수 있다.
- 데이터베이스 독립성: 업무 규칙은 데이터베이스에 결합되지 않는다.
- 모든 외부 에이전시에 대한 독립성: 실제로 업무 규칙은 외부 세계와의 인터페이스에 대해 전혀 알지 못한다

 

 

MSA(마이크로 서비스 아키텍처)의 문제점과 클린 MSA

 

잘 나가는 자사 서비스 기업, 유니콘 기업들이 MSA를 너도나도 도입하면서 다른 개발 회사들과 개발자들도 모두 MSA를 자신의 기술로 만들기 위해 노력하는 듯합니다. 그런데 사실 MSA는 개발 시간뿐만 아니라 시스템 자원 측면에서도 비용이 많이 들어갑니다. 물론 비용을 감당할 수 있다면 MSA는 관심사의 분리와 자원의 분리로 인해 유지보수성을 가져다줄 수 있습니다. 하지만 이것도 안정적인 설계가 되었을 때의 이야기입니다. 

 

MSA를 모노리틱 구조와 떼어내어 생각할 수 있는 동떨어진 별개의 신기술로 생각해서는 안됩니다. 클린 아키텍처를 가진 시스템은 다음과 같습니다. 시스템은 초반에 모노리틱 구조로 출발할 수 있습니다. 이후에는 시스템이 점점 커지면서 독립적으로 배포 가능한 단위들의 집합으로 성장하고, 마이크로 서비스 수준까지 성장할 수 있습니다. 그리고 이것을 나중에 상황이 바뀌었을 때 다시 모노리틱 구조로 합칠 수도 있어야 합니다.

 

 

지금까지의 내용은 로버트 C. 마틴클린 아키텍처를 요약해본 것으로 책에서 더 자세한 내용을 볼 수 있습니다.

 

 

 

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기