Clean Architecture

김세영·2021년 12월 26일
0

기타 정리

목록 보기
2/4
post-thumbnail

1부 - 소개

1. 설계와 아키텍처란?

  • 개발 조직은 항상 소프트웨어 아키텍처의 품질을 심각하게 고민해야 한다.
  • 개발자는 조직에 스며든 과신을 인지하여 방지해야 한다.
  • 품질을 심각하게 고민할 수 있으려면, 좋은 아키텍처가 무엇인지 이해해야 한다.

2. 두 가지 가치에 대한 이야기

  • 행위 (Behavior)
    요구사항에 맞춰 코드를 짜고 버그를 수정하는 일
    긴급하지만 중요하지 않다.

  • 아키텍처
    기계의 행위를 쉽게 변경할 수 있도록 시스템을 구축하는 것
    긴급하진 않지만 중요하다.

행위를 긴급하고 중요한 일로 만들지 말아야 한다.

2부 - 프로그래밍 패러다임

3. 패러다임 개요

  • 구조적 프로그래밍
    제어 흐름의 직접적인 전환에 대해 규칙을 부과한다.

  • 객체 지향 프로그래밍
    제어 흐름의 간접적인 전환에 대해 규칙을 부과한다.

  • 함수형 프로그래밍
    할당문에 대해 규칙을 부과한다.

이 세 가지 패러다임은 프로그래머를 편리하게 하는 것이 아니라,
할 수 없는 것들(규칙)을 제시하여 프로그래머의 권한을 뺏어간다.

세 가지 패러다임과, 아키텍처의 세 가지 관심사는 서로 연관된다.
(함수, 컴포넌트 분리, 데이터 관리)

4. 구조적 프로그래밍

goto문으로 인해 입증이 불가능한 프로그램을
if-then-else, do-while-until 등으로, 증명 가능한 세부 기능 집합으로 분해한다.

이 집합들이 완벽하고, 항상 참이라고는 말할 수 없지만
거짓임을 증명하려는 테스트들이 모두 실패하는 것으로 목표한 만큼 참이라고 여길 수 있다.

모듈, 컴포넌트, 서비스를 쉽게 테스트할 수 있도록 노력해야 하고,
이를 위해 구조적 프로그래밍과 유사한 규칙들을 받아들여 활용해야 한다.

5. 객체 지향 프로그래밍

객체 지향이 무엇인가? 에 대한 답은 여러 가지가 있지만,
아키텍트 관점에서의 정답은 다형성을 이용하여 의존성에 대한 절대적인 권한을 획득하는 것이다.

각각의 모듈을 분리할 수 있고, 의존성 역전을 가능하게 하여
수준 별 모듈에 대해 독립성을 보장하여 개별 배포가 가능하게 한다.

6. 함수형 프로그래밍

클로저(Clojure)와 같은 함수형 언어에서는 변수가 변경되지 않는다.
이러한 불변성이 있다면 race condition, deadlock, concurrent update
등의 문제가 발생하지 않는다.

따라서 현명한 아키텍처는 가능한 한 많은 코드를 불변 컴포넌트로 옮기고,
가변 컴포넌트에서 최대한 코드를 빼내야 한다.

3부 - 설계 원칙 (SOLID)

7. SRP: 단일 책임 원칙 (Single Resposibility)

하나의 클래스는 하나의 책임만을 져야 한다.

  • 우발적 중복
    서로 성격이 많이 다른 액터들이 같은 모듈을 공유한다면,
    한 액터가 모듈을 변경했을 때 다른 액터가 영향을 받는다.

  • 병합
    하나의 기능에 대해 서로 다른 액터들이 코드를 변경했을 때,
    원하는 기능이 각자 다르므로 병합 충돌이 발생한다.

이를 해결하는 방법은 메서드를 각기 다른 클래스로 이동하는 방법 뿐이다.

8. OCP: 개방-폐쇄 원칙 (Open-Closed)

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

  • 인터페이스를 통해 정보 은닉을 하여 시스템이 많은 영향을 받게 하지 않는다.
  • 컴포넌트 단위로 시스템을 분리하고, 저수준 컴포넌트의 변경이
    고수준 컴포넌트에 영향을 미치지 않도록 의존성 계층구조를 만들어야 한다.

9. LSP: 리스코프 치환 원칙 (Liskov Substitution)

상위 클래스를 사용하는 코드가 이를 하위 클래스로 변경해도 정상적으로 동작해야 한다.

  • 리스코프 치환 원칙을 조금이라도 위배한다면
    전체 아키텍처가 오염되어 상당량의 메커니즘을 추가할 수 밖에 없다.

10. ISP: 인터페이스 분리 원칙 (Interface Segregation)

클라이언트는 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.

  • 큰 단위의 인터페이스를 작게 분리하여 각 클라이언트가 사용할 메서드만 제공한다.

11. DIP: 의존성 역전 원칙 (Dependecy Inversion)

상위 모듈은 하위 모듈에 의존하지 않고, 하위 모듈이 상위 모듈의 추상화된 인터페이스에 의존해야 한다.

  • 소스 코드 의존성과 제어 흐름이 반대 방향으로 역전된다.
  • 추상 개념이 구현에 의존하는 것이 아닌, 구현이 추상 개념에 의존해야 한다.

4부 - 컴포넌트 원칙

12. 컴포넌트

  • 시스템의 구성 요소로, 배포할 수 있는 가장 작은 단위이다. (iOS는 .ipa)
  • 잘 설계된 컴포넌트는 독립적으로 개발 가능하고, 배포 가능해야 한다.

13. 컴포넌트 응집도

컴포넌트 응집도의 세 가지 원칙

  • REP: 재사용/릴리즈 등가 원칙, Reuse/Release Equivalance Principle
    재사용 단위는 릴리즈 단위와 같다.
    • 단일 컴포넌트는 응집성 높은 클래스와 모듈들로 구성되어야 한다.
    • 하나의 컴포넌트로 묶인 클래스와 모듈은 반드시 함께 릴리즈할 수 있어야 한다.
  • CCP: 공통 폐쇄 원칙, Common Closure Principle
    동일한 이유로 동일한 시점에 변경되는 클래스는 같은 컴포넌트로 묶는다.
    • SRP를 컴포넌트 관점에서 본 것 (단일 컴포넌트는 하나의 책임만을 가져야 한다.)
    • 변경된 컴포넌트에 의존하지 않는 컴포넌트는 전혀 변경에 영향을 받지 않는다.
  • CRP: 공통 재사용 원칙, Common Reuse Principle
    컴포넌트 사용자들을 필요하지 않는 것에 의존하도록 강요하지 않아야 한다.
    • 같이 재사용되는 경향이 있는 클래스와 모듈들은 같은 컴포넌트에 포함해야 한다.
    • 의존하는 컴포넌트가 있다면 해당 컴포넌트의 모든 클래스에 의존함을 인지해야 한다.
    • ISP를 컴포넌트 관점에서 본 것 (필요하지 않은 것에 의존하지 않아야 한다.)
  • 컴포넌트 응집도에 대한 균형 다이어그램

    REP, CCP는 포함 원칙, CRP는 배제 원칙이며, 세 원칙이 서로 상충된다.
    • 개발자가 현재 관심을 기울이는 방향으로 삼각형에서 적절한 위치를 찾아야 한다.
    • 시간이 흐르면서 위치가 변한다는 사실을 이해해야 한다.

14. 컴포넌트 결합

컴포넌트 사이의 관계를 설명하는 세 가지 원칙

  • ADP: 의존성 비순환 원칙, Acyclic Dependencies Principle
    컴포넌트 의존성 그래프에 순환이 없어야 한다.
    • 의존성 그래프에 순환이 생기는 순간, 각각의 컴포넌트의 개별적인 배포와 테스트는 불가능하다.
    • 어떤 순서로 빌드해야 할지 파악할 수 없다.
  • SDP: 안정된 의존성 원칙, Stable Dependencies Principle
    안정성의 방향으로(더 안정된 쪽에) 의존해야 한다.
    • 변경이 쉽지 않은 컴포넌트가 변동이 예상되는 컴포넌트에 의존하게 만들면 절대 안 된다.
    • 위와 같은 상황이 일어나면 DIP로 해결할 수 있다. (추상 컴포넌트)

      안정성 (Stability)

      어떠한 컴포넌트 안쪽으로 들어오는 의존성이 많다면 사소한 변경이 발생하더라도
      그 컴포넌트는 모든 의존성을 신경쓰면서 변경해야 하므로 변경이 쉽지 않다.
      이럴 때 컴포넌트가 안정적이라고 할 수 있다.

  • SAP: 안정된 추상화 원칙, Stable Abstractions Principle
    컴포넌트는 안정된 정도만큼만 추상화되어야 한다.
    • 고수준 정책을 안정된 컴포넌트에 위치시키면, 정책을 포함하는 소프트웨어는 수정하기 어려워진다.
      이를 피하기 위해 OCP 정책을 사용한다.
    • 안정적인 컴포넌트라면 반드시 인터페이스와 추상 클래스로 구성되어 쉽게 확장할 수 있어야 한다.

5부 - 아키텍처

15. 아키텍처란?

아키텍처의 주된 목적은 시스템이 잘 작동하도록 하는 것이 아니다.

주된 목적은 시스템의 생명주기를 지원하는 것이다.
시스템을 쉽게 이해하고, 개발하며, 유지보수하고, 배포하게 해주는 것이 좋은 아키텍처이다.

  • 시스템의 수명과 관련된 비용은 최소화, 프로그래머의 생선성을 최대화

개발

아키텍처는 개발 팀들이 시스템을 쉽게 개발할 수 있도록 뒷받침되어야 한다.
개발 팀들의 규모, 방식에 따라 적절한 아키텍처를 결정해야 한다.

배포

개발 초기에는 배포 전략을 거의 고려하지 않는다.

개발하기에만 편한 아키텍처로 개발한다면 배포가 몹시 어려워지고,
배포가 어려우면 시스템의 유용성이 떨어지게 된다.

운영

시스템 아키텍처는 필수 행위를 일급 엔티티로 격상하고,
이들이 개발자에게 주된 목표로 인식되도록 해야 한다.

유지보수

신중하게 아키텍처를 만들면, 유지보수에 필요한 탐사 비용을 크게 줄일 수 있다.

  • 탐사 (spelunking)
    기존 소프트웨어에 새로운 기능을 추가하거나 결함을 수정할 때,
    어디를 고치는 것이 최선일지, 어떤 전략을 선택하는 것이 최적일지를 결정할 때 드는 비용

컴포넌트로 분리하고, 안정된 인터페이스를 두어 서로를 격리한다면
추가될 기능에 대한 길을 밝히고 장애가 발생할 위험을 줄일 수 있다.

선택사항 열어두기

세부사항을 정책으로부터 신중하게 가려내고,
정책이 세부 사항과 결합되지 않도록 엄격하게 분리한다.

이를 통해 정책은 세부 사항에 관한 어떠한 것도 알지 못하게 되고,
세부 사항에 의존할 수 없게 된다.

좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다.

16. 독립성

좋은 아키텍처는 다음을 지원해야 한다.

  • 유스케이스, 운영, 개발, 배포

유스케이스

시스템의 아키텍처는 시스템의 의도를 지원해야 한다.

좋은 아키텍처가 행위를 지원하기 위해 할 수 있는 일 중 가장 중요한 사항은
행위를 명확히 하고 외부로 드러내며,
이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다.

운영

아키텍처는 요구와 관련된 유스케이스에 걸맞은 처리량과 응답 시간을 보장해야 한다.

컴포넌트를 격리하고, 통신 방식을 특정 형태로 제한하지 않는다면
운영에 필요한 요구사항이 바뀌더라도 기술 스펙트럼 사이를 전환하는 일이 훨씬 쉬워진다.

개발

아키텍처는 개발 환경을 지원하는 데에 핵심적인 역할을 수행한다.

콘웨이의 법칙
시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것이다.

각 팀이 독립적으로 개발할 수 있는 아키텍처를 확보하여
개발하는 동안 각 팀들이 서로를 방해하지 않도록 해야 한다.

배포

좋은 아키텍처는 시스템이 빌드된 후 즉각 배포될 수 있도록 해야 한다.

이를 위해 시스템을 컴포넌트 단위로 격리시켜야 한다.
마스터 컴포넌트는 시스템 전체를 하나로 묶고,
각 컴포넌트를 올바르게 구동하고 통합하고 관리해야 한다.

선택사항 열어놓기

목표는 뚜렷하지 않을 뿐 아니라, 시시각각 변한다.
이러한 변화 속에서도 좋은 아키텍처는 컴포넌트 구조와 관련된 관심사들 사이에서
균형을 맞추고, 모든 컴포넌트를 만족시킬 수 있다.

좋은 아키텍처는 선택사항을 열어 둠으로써, 변경이 필요할 때
어느 방향으로든 쉽게 변경할 수 있도록 한다.

계층 결합 분리

단일 책임 원칙과 공통 폐쇄 원칙을 적용하여 시스템을 적절히 분리하고 묶어야 한다.

시스템을 결합되지 않은 수평적인 계층으로 분리해야 한다.
계층으로는 UI / 애플리케이션에 특화된 업무 규칙 / 애플리케이션과는 독립적인 업무 규칙 / 데이터베이스
등이 있다.

유스케이스 결합 분리

유스케이스는 서로 다른 이유로 변경될 수 있다. (주문 추가용 유스케이스, 주문 삭제용 유스케이스)
따라서 시스템을 수평적인 계층으로 분리하면서, 수직적인 유스케이스로 분할할 수 있다.

서로 다른 이유로 변경되는 요소들을 격리해 놓으면,
기존 요소에 지장을 주지 않고 새로운 유스케이스를 계속 추가할 수 있게 된다.

개발 독립성

컴포넌트가 완전히 분리되면 팀 사이의 간섭이 줄어든다.
계층과 유스케이스의 결합이 분리되는 한 시스템의 아키텍처는
팀 구조를 뒷받침해 줄 것이다.

배포 독립성

결합을 제대로 분리했다면 운영 중인 시스템에서도 계층과 유스케이스를 hot-swap할 수 있다.

중복

  • 진짜 중복
    한 인스턴스가 변경되면, 동일한 변경을 그 인스턴스의 모든 복사본에 적용해야 한다.

  • 우발적 중복
    서로 같아보이는 코드가 서로 다른 속도와 이유로 변경된다면 그 코드들은 중복이 아니다.

진짜 중복과 우발적 중복을 신중하게 구분해야 하고,
함부로 중복을 통합하지 말아야 한다.

결합 분리 모드

  • 소스 수준 분리 모드
    소스 코드 모듈 사이의 의존성을 제어
  • 배포 수준 분리 모드
    배포 가능한 단위들 사이의 의존성을 제어
  • 서비스 수준 분리 모드
    데이터 구조 단위까지 의존 수준을 낮추고, 네트워크 패킷을 통해서만 통신

17. 경계: 선 긋기

관련이 있는 것과 없는 것 사이에 선을 긋는다.
컴포넌트 단위로 시스템을 분리하고, 사이의 화살표가 핵심 업무를 향하도록
컴포넌트의 소스를 배치한다.

이는 의존성 역전 원칙, DIP안정된 추상화 원칙, SAP을 응용한 것이다.

18. 경계 해부학

profile
초보 iOS 개발자입니다ㅏ

0개의 댓글