클린 아키텍처 : 소프트웨어 구조와 설계의 원칙을 읽어봤습니다

thinkySide·2024년 6월 23일
14
post-thumbnail

🧐 책을 읽게 된 이유

iOS 개발자로 성장해나가며 최근 가장 관심 있었던 주제는 단연 '아키텍처', 즉 구조 설계였다. 다른 사람들과 함께 협업하는 것, 어제의 나와 프로덕트를 만들어나가는 것, 개발자로서의 의미 모두 구조 설계라는 맥락으로 귀결됐고 배움의 필요성을 절실히 느끼게 되었다.

그러던 중 애플 아카데미 내 '클린 아키텍처 책 읽기' 스터디에 참여하게 되었고, 자연스레 책을 접하게 되었다. 꽤나 폭력적으로 생긴 표지(?)와 어렵다는 악명,, 을 뒤로하고 끝까지 읽을 수 있었던 이유는 함께 읽어서였던 것 같다. 열정적으로 서로의 생각을 나누고, 토론하며 성장해나감을 느꼈고 이 과정을 이끌어준 모수에게 감사 인사를 올린다!

🐰 느낀점 적어보기

처음 클린 아키텍처라는 단어를 들었을 때 나는 MVC, MVVM과 같은 아키텍처를 떠올렸다. 하지만 책을 모두 읽고 이제는 확실히 이야기할 수 있어졌다. 클린 아키텍처는 MVC와 MVVM과 같이 정형화된 아키텍처를 이야기하지 않는다. (사실 이 부분에 대해서는 이견이 있을 수도 있는데, 위 두 아키텍처도 사용하는 방식이 다양하기 때문이다. 하지만 내가 집중해서 말하고자 하는 것은 Model, View, Controller와 같은 명확한 기준점을 제시하지 않는다는 뜻이다.)

그럼 클린 아키텍처는 무엇일까? 클린 아키텍처를 검색하면 등장하는 이미지에서 크게 4가지 레이어로 분리하는 것으로 설명할 수 있을까? 나는 꼭 그렇지 않다고 느꼈다. 내가 이해한 클린 아키텍처의 핵심은 다음과 같다.

의존성의 방향은 저수준에서 고수준을 바라봐야 한다는 것이다. 저수준이라 함은 세부사항 즉 UI, Framework, DB 혹은 iOS 개발자의 입장에서는 백엔드 팀이 전달해 주는 API 사용을 위한 DTO와 같은 외부 요인들이다. 이들은 변화되기 쉽다.(변화될 수밖에 없는 것들이기도 하다)

고수준이라 함은 핵심 업무 규칙 즉 도메인, 유스케이스, 엔티티와 같은 것들이다. 이 단어들을 말로 풀어보자면 개발팀이 아니어도 이야기할 수 있는 것들이고, 바뀌어서는 안되는 핵심적인 것들도 포함되어 있다. (인스타그램에서 사진을 업로드하고 이를 피드에 업로드하는 것은 팀 모두가 함께 결정한 사항이었을 것이다. 반대로 그게 어떻게 구현되는지는 개발팀이 아니라면 함께 결정하지 않아도 된다. 또한 인스타그램의 운영 방식이 180도 바뀌는 게 아닌 이상 해당 업무 규칙은 변화되지 않을 것이다) 이들은 저수준과 비교해 상대적으로 변화되기 어렵다.

소프트웨어는 왜 소프트웨어인가? 하드웨어와 소프트웨어의 가장 큰 차이점은 '변화'에 있다. 하드웨어는 한번 공장에서 생산되어 나오면 다시 변화되기 매우 어렵다. 소프트웨어는 어떤가? 말 그대로 부드럽고 유연하게 변화가 가능하다. 즉, 소프트웨어의 본질은 변화에 있는 것이다.

그럼 우리는 자연스레 변화하기 쉬운 것(저수준)들이 변화하기 어려운 것(고수준) 들에 의존해야 할 것임을 짐작할 수 있다. 소프트웨어는 변화하기 때문이다! 고수준이 저수준을 바라보고 있거나 이들이 서로를 바라보고 있는 상황을 상상해 보자. 디자인 팀에서 UI 변경을 요청했는데 우리가 건드려야 할 곳이 깊은 고수준 규칙들인 상황과 기획 팀에서 새로운 기능 추가 요청을 했는데 강하게 결합되어 어디를 건드려야 할지 모르는 상황들 말이다.

소프트웨어 개발자는 이러한 변화의 비용을 관리해야 할 책임이 있다. 작은 변화 하나에도 한숨을 쉬며 코드를 바라보는 일은 없어야 한다는 것이다.

결국 다시 돌아와 나는 의존성의 방향이 저수준에서 고수준을 향할 수 있는 설계가 클린 아키텍처의 핵심이고, 이는 레이어의 개수와는 무관할 것이라 생각한다. 레이어의 개수는 이를 얼마나 더 명확하게 가져가야 하는가의 문제이지, 모든 곳에서 적용할 수 있는 일관된 것은 아니라 생각하기 때문이다. (아키텍처 설계의 비용에 대해서도 생각해 봐야 하는 문제이다)

마지막으로 클린 아키텍처를 공부하며 새롭게 안 사실 중 한 가지는 아키텍처를 바라보는 사람들의 시각에 의구심이 섞여있을 때가 많다는 것이다. 여러 가지 이유로 필요성에 대해 의구심을 품곤 하는 데 대개 설계 비용, 러닝 커브, 팀원 간의 설득, 구현 자체에 초점 등이 있다.

아키텍처는 절대적이지 않다. 어느 유명한 아키텍쳐를 우리의 프로덕트에 적용한다고 해도 들어맞지 않을 수밖에 없을 것이다. 결국 우리는 탐구해야 한다. 우리 프로덕트가 진정으로 전달하고자 하는 요구사항이 무엇인지, 어떤 변화를 일으킬 수 있는지 끝없이 고민해 우리만의 아키텍처를 만들어나갈 때 비로소 그 진가를 확인할 수 있을 것이다.

소프트웨어 개발자로서 이러한 탐구를 하지 않는다면 프로덕트에서 개발자가 가져야 할 책임은 구현 그뿐일 것이다. 나는 그렇게 생각하지 않는다. 더 많은 책임을 가지고 오기 위해 투쟁해야 한다.

저자는 이렇게 말한다. "아키텍처는 종착지가 아니라 여정에 더 가까우며, 고정된 산출물이 아니라 계속된 탐구과정에 더 가까움을 이해해야 한다."

개발자로서의 의미를 견고하게 가져갈 수 있었던 시간이었다!
(아직 부족한 부분이 많아 기회가 된다면,,, 다회독을 해보려 한다!)

📝 메모 내용 정리

추천사

아키텍처는 시스템을 구체화하는 중요한 설계 결정을 표현하며, 그 결정의 중요도는 변경에 드는 비용으로 측정된다.

아키텍처는 종착지가 아니라 여정에 더 가까우며, 고정된 산출물이 아니라 계속된 탐구과정에 더 가까움을 이해해야 좋은 아키텍쳐가 만들어진다.

서문

소프트웨어 아키텍처의 규칙은 다른 모든 변수에 '독립적'이다.

1장: 설계와 아키텍처란?

소프트웨어 아키텍처의 목표는 필요한 시스템을 만들고 유지보수하는데 투입되는 인력을 최소화하는데 있다.

설계품질을 재는 척도는 고객의 요구를 만족시키는데 드는 비용을 재는 척도와 다름없다.

클린 아키텍처 책의 목표?
→ 비용은 최소화하고 생산성은 최대화할 수 있는 설계와 아키텍처를 가진 시스템을 만드려면 이러한 결과로 이끌어줄 시스템 아키텍처가 지닌 속성을 알고 있어야 한다.

2장: 두 가지 가치에 대한 이야기

행위?
기능명세서나 요구사항 문서를 구체화할 수 있게 돕는 것.

구조?(아키텍처)
행위를 쉽게 변경할 수 있게 돕는 것.

업무 관리자는 보통 아키텍처의 중요성을 평가할만큼 능력을 겸비하지 못하기 때문에 개발자는 딜레마에 빠진다. 소프트웨어 개발자를 고용하는 이유는 바로 이 딜레마를 해결하기 위해서다. 따라서, 기능의 긴급성이 아닌 아키텍처의 중요성을 설득하는 일은 소프트웨어 개발팀이 마땅히 책임져야 한다.

소프트웨어 개발자는 소프트웨어를 안전하게 보호해야 할 책임이 있다!

아키텍처가 후순위가 되면 시스템을 개발하는 비용이 더 많이 들고, 일부 또는 전체 시스템에 변경을 가하는 일이 현실적으로 불가능해진다.

3장: 3가지 패러다임

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

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

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

패러다임은 무엇을 해야할지 말하기 보다, 무엇을 해서는 안되는지를 말해준다.

4장: 구조적 프로그래밍

구조적 프로그래밍
→ 프로그래밍에서 반증 가능한 단위를 만들어낼 수 있는 능력

소프트웨어 아키텍트는 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록(테스트 하기 쉽도록) 만들기 위해 분주히 노력해야 한다.
→ 이를 위해 구조적 프로그래밍과 유사한 제한적인 규칙들을 받아들여 활용해야 한다.

5장: 객체지향 프로그래밍

객체 지향 프로그래밍 핵심 키워드
1. 캡슐화
2. 상속
3. 다형성

객체지향 언어가 다형성을 안전하고 편리하게 제공한다는 사실은 소스코드의 의존성 역전을 어디서든 할 수 있다는 뜻이다.

객체지향 언어로 개발된 시스템을 다루는 소프트웨어 아키텍트는 시스템의 소스코드 의존성 전부에 대해 방향을 정할 수 있는 절대적인 권한을 얻는다.

6장: 함수형 프로그래밍

어플리케이션을 제대로 구조화하려면 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리해야 한다는 것이다.

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

7장: SRP 단일 책임 원칙

SRP?
하나의 모듈은 하나의, 오직 하나의 액터(변경을 요청하는 한 명 이상의 사람들)에 대해서만 책임져야 한다.

이를 해결하기 위한 방법으로 Facade 패턴이 있다.

8장: OCP 개방-폐쇄 원칙

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

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

11장: DIP 의존성 역전 원칙

DIP를 논할 때 운영체제나 플랫폼 같이 안정성이 보장된 환경에 대해서는 무시하는 편이다.

우리는 이들 환경에 대해서는 의존성을 용납하는데, 변경되지 않는다면 의존할 수 있다는 사실을 이미 알고 있기 때문이다.

우리가 의존하지 않도록 피하고자 하는 것은 바로 변동성이 큰 구체적인 요소다.

뛰어난 설계자라면 인터페이스의 변동성을 낮추기 위해 애쓴다.

12장: 컴포넌트

컴포넌트는 시스템의 구성 요소로 배포할 수 있는 가장 작은 단위다.

REP: 재사용/릴리스 등가 원칙
CCP: 공통 폐쇄 원칙
CRP: 공통 재사용 원칙

CCP?
동일한 시점에서 동일한 이유로 변경되는 것들을 한데 묶어라. 서로 다른 시점에 다른 이유로 변경되는 것들을 서로 분리해라.

CRP?
필요하지 않은 것에 의존하지 말라.

14장: 컴포넌트 결합

ADP?
의존성 비순환 원칙, 구성 요소 간의 의존성을 파악할 수 있으면 시스템을 빌드하는 방법을 알 수 있다.

의존성 구조에 순환이 발생하는지를 항상 관찰해야 한다.

순환이 발생하면 어떤 식으로든 끊어야 한다. 이 말을 때론 새로운 컴포넌트를 생성하거나 의존성 구조가 더 커질 수 있음을 의미한다.
→ 의존성 그래프가 눈에 보이지 않는 상황에서는 새로운 컴포넌트를 생성하거나 의존성을 위해 프로토콜을 생성하는 과정이 인지하기 어렵거나 필요성을 느끼지 못할 때가 많았는데, 자연스러운 일임과 동시에 꼭 필요한 일이라는 것을 알 수 있었음!

컴포넌트는 시스템에서 가장 먼저 설계할 수 있는 대상이 아니며, 오히려 시스템이 성장하고 변경될 때 함께 진화한다.

변경이 쉽지 않은 컴포넌트가 변동이 예상되는 컴포넌트에 의존하게 만들어서는 절대로 안된다.

SDP: 안정성의 방향으로(더 안정된 쪽) 의존하라.
안지켰을 때! → 당신의 모듈에서는 단 한 줄의 코드도 변경되지 않았지만 어느 순간 갑자기 당신의 모듈을 변경하는 일은 상당히 도전적인 일이 되어버린다.

SAP: 컴포넌트는 안정될 정도로만 추상화 되어야 한다.

15장: 아키텍처란?

소프트웨어를 부드럽게 만든다.

모든 소프트웨어 시스템은 주요한 2가지 구성요소로 분해할 수 있다.
1. 정책 - 업무 규칙, 업무 절차, 비즈니스 로직
2. 세부사항 - DB, 시스템, 서버, 프레임워크

아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고, 동시에 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는데 있다.

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

좋은 아키텍트?
1. 세부사항을 정책과 분리
2. 정책이 세부사항과 결합되지 않게 엄격히 분리
3. 정책은 세부사항에 관한 어떤 지식도 갖지 못함(의존하지 않게 됨)

16장: 독립성

시스템을 수평적 계층으로 분할하면서 동시에 해당 계층을 가로지르는 얇은 수직적인 유스케이스로 시스템을 분할할 수 있다.

중복의 함정?
중복으로 보이는 두 코드 영역이 각자의 경로로 발전한다면, 즉 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는 진짜 중복이 아니다. 몇년이 지나 다시보면 두 코드가 매우 다르다는 사실을 알게 될 것이다. 이러한 이유로 해당 코드를 통합하지 않도록 유의해야 한다.
→ 동일한 기능인 것 같아 중복을 제거 후 하나로 사용하려 했던 경험이 많았는데, 이것이 진짜 동일한 기능을 제공하는 것인지 면밀히 생각해봐야겠다!

중복이 진짜 중복인지 확인하라.

17장: 경계 / 선 긋기

GUI는 다른 종류의 인터페이스로 얼마든지 교체 가능하고, Business Rules은 전혀 개의치 않는다.

아키텍처에 경계선을 그리려면?
1. 시스템을 컴포넌트 단위로 분리
2. 일부는 핵심 업무 규칙에 해당
3. 나머지는 플러그인(핵심 업무와 직접적인 연관 X)
4. 컴포넌트 사이의 화살표가 핵심 업무를 향하도록

의존성 화살표는 저수준 세부사항에서 고수준 추상화를 향하도록 배치한다.

18장: 경계 해부학

저수준은 반드시 고수준 서비스의 플러그인이 되어야 한다.

고수준 서비스의 소스코드에는 저수준 서비스를 특정짓는 어떤 물리적인 정보도 포함되어서는 안된다.

19징: 정책과 수준

저수준 → 고수준 의존!

시스템의 입출력과 멀게 위치할수록 정책의 수준은 높아진다.

소스코드 의존성은 그 수준에 따라 결합되어야 하며, 데이터 흐름을 기준으로 결합되어서는 안된다.

20장: 업무 규칙

업무 규칙?
사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차
(컴퓨터로 구현했는지는 중요하지 않다)

핵심 규칙과 핵심 데이터는 본질적으로 결합되어 있기 때문에 객체로 만들 좋은 후보이다. → 엔티티

유스케이스는 자동화된 시스템이 사용되는 방법을 설명한다 → 처리 단계를 기술한 것.

유스케이스는 애플리케이션에 특화된 업무 규칙을 설명한다.

유스케이스는 엔티티 내부의 핵심 업무 규칙을 어떻게, 그리고 언제 호출할지를 명시하는 규칙을 담는다.

유스케이스는 시스템이 사용자에게 어떻게 보이는지를 설명하지 않는다.

21장: 소리치는 아키텍처

소프트웨어 애플리케이션의 아키텍처도 애플리케이션의 유스케이스에 대해 소리 쳐야 한다.

22장: 클린 아키텍처

지난 수십년간 시스템 아키텍처와 관련된 여러가지 아이디어는 ...(중략)... 이들의 목표는 모두 같은데, 바로 관심사의 분리다. 소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성할 수 있었다.
→ 결국 어떤 시스템 아키텍처든 그 목표는 '관심사의 분리'로, 이 목표가 지켜질 수 있다면 어떤 방법을 사용해도 무방할 것이라 생각한다!

아키텍처가 시스템에 지니도록 하는 특징 5가지
1. 프레임워크 독립성: 프레임워크의 존재 여부에 의존하지 않는다.
2. 테스트 용이성: 업무 규칙은 UI, DB. Server 등이 없어도 테스트 가능하다.
3. UI 독립성: 시스템의 나머지 부분을 변경하지 않고도 UI를 쉽게 변경 가능하다.
4. DB 독립성: 오라클을 몽고 DB 등으로 교체할 수 있다. 또한 업무 규칙은 데이터베이스에 결합되지 않는다.
5. 모든 외부 에이전시에 대한 독립성: 업무 규칙은 외부 세계의 인터페이스에 대해 '전혀' 알지 못한다.

표시한 원들은 그저 개념을 설명하기 위한 하나의 예시일 뿐이다. 네 개보다 더 많은 원이 필요할 수도 있다. 하지만 어떤 경우에도 의존성 규칙은 적용된다.
→ 책에서는 원이 더 많을 수도 있다고 했지만, 마찬가지로 더 적을 수도 있을 것이라 생각했다. 의존성 규칙을 올바르게(소스 코드 의존성은 항상 안쪽을 향한다는) 적용할 수 있다면 원의 개수와 상관없이 고수준 정책들을 안전하게 지킬 수 있을테니 말이다!

소스코드 의존성은 반드시 안쪽으로, 고수준 정책을 향해야 한다.

우리는 외부 원에 위치한 어떤 것도 내부의 원에 영향을 주지 않기를 바란다.

엔티티 레이어

  • 전사적인 핵심 업무 규칙을 캡슐화
  • 메서드를 가지는 객체이거나, 일련의 데이터 구조와 함수의 집합일 수 있음.
  • 엔티티를 재사용 할 수 있다면, 그 형태는 그다지 중요하지 않음.

유스케이스 레이어

  • 애플리케이션에 특화된 업무 규칙
  • 엔티티로 들어오고 나가는 데이터 흐름 조정.
  • 엔티티의 핵심 업무 규칙을 사용해 유스케이스의 목적을 달성하도록 이끈다.

24장: 부분적 경계

아키텍처 경계를 완벽하게 만드는 데는 비용이 많이 든다 ...(중략)... 많은 경우에 뛰어난 아키텍트라면 이러한 경계를 만드는 비용이 너무 크다고 판단하면서도, 한편으로는 나중에 필요할 수 있으므로 이러한 경계에 필요한 공간을 확보하기 원할 수 있다.
→ 클린 아키텍처를 실제 프로젝트에 적용해보며 가장 많이 고민했던 부분이다. 우리 정도 규모에 이 정도 레이어를 분리할 필요가 있을까,,, 고민했던 것 같다.

완벽한 형태의 아키텍처 경계는 양방향으로 격리된 상태를 유지해야 하므로 쌍방향 인터페이스를 사용한다.

아키텍처 경계가 언제, 어디에 존재해야 할지, 그리고 그 경계를 완벽하게 구현할지 아니면 부분적으로 구현할지를 결정하는 일 또한 아키텍트의 역할이다.

25장: 계층과 경계

우리의 목표는 경계의 구현 비용이 그걸 무시해서 생기는 비용보다 적어지는 바로 그 '변곡점'에서 경계를 구현하는 것이다. 목표를 달성하려면 빈틈없이 지켜봐야 한다.

29장: 클린 임베디드 아키텍처

프로그래머가 오직 앱이 동작하도록 만든느 일만 신경 쓴다면 자신의 제품과 고용주에게 몹쓸 짓을 하는 것이다.
→ 소프트웨어의 본질은 변화에 있다고 생각하고, 그 소프트웨어를 만드는 것은 우리 개발자이기에 우리는 아키텍처에 고나심을 기울여야 한다!

34장: 빠져있는 장

아키텍처 원칙을 강제할 때 자기 규율이나 컴파일 후처리 도구를 이용하지 말고, 반드시 컴파일러에 의지할 것을 권장한다.

맺는 글

무엇보다 가장 중요한 일은 클린 아키텍처에 대해 이야기하는 것이다. 팀과 함께 클린 아키텍처에 대해 이야기하라. 폭넓은 개발자 커뮤니티와 함꼐 이야기하라. ...(중략)... 당신이 클린 아키텍처를 제대로 이해하게 되었다면, 시간을 내어 다른 사람들도 제대로 이해할 수 있도록 도와라, 선행을 베풀어라.
→ 와닿는 말이다. 나조차도 클린 아키텍처에 대해 제대로 이해하지 못했지만, 그만큼 잘 알고 계시는 다른 분들께 용기내어 여쭤보려 하고, 내가 이해하게 되었을 때는 팀원들 구성원들과 함께 적극적으로 나눌 것이다!

profile
UX 한스푼 넣은 iOS 디발자 한톨 / Apple Developer Academy @POSTECH 3기 / 티스토리 이사중,, https://thinkyside.tistory.com/

3개의 댓글

comment-user-thumbnail
2024년 7월 6일

프론트엔드 개발만 하다가 요즘 들어 사이드 프로젝트로 앱 개발을 조금 해보고 싶은 일인입니다.. 앱 개발을 처음 입문 하려면 플러터가 좋은 선택일까요 아니면 Swift가 좋은 선택일까요? 개인적으론 안드로이드 말고 iOS 개발을 하고 싶긴합니다 ㅎㅎ

1개의 답글

관련 채용 정보