Skip to main content

클린 아키텍처 정리

· 15 min read
Jae Jun, Jo

늦었지만 클린 아키텍처라는 책을 읽었습니다.

좋은 내용이 많았지만 그 중 기억에 남는 부분을 남겨보겠습니다.

설계와 아키텍처

좋은 코드란 무엇일까? 개발자라면 이런 물음을 한번쯤은 고민해보셨을 것 같습니다.

저는 좋은 코드를 일관성 있는 코드라고 정의합니다.

일관성 있는 코드는 해당 제품의 개발 방식을 쉽게 익힐 수 있으며

팀의 개발 방식을 하나로 통일 할 수 있게 됩니다.

통일되지 않은 개발방식은 "누군가만" 쉽게 건드릴 수 있다면

통일된 개발 방식은 "모두가" 건드릴 수 있는 코드를 만들어 나갈 수 있습니다.

일관성 있는 코드를 작성하는 방법은 아키텍처를 잘 정의하는 것에서부터 시작합니다.

이 책의 설계와 아키텍처 장에서도 아키텍처를 잘 설계하는 것의 중요함에 대해 이야기합니다.

두가지 가치에 대한 이야기

이 장에서는 두가지 가치, 행위와 아키텍처에 대해 이야기합니다.

행위는 기능이 잘 작동하는가를 말합니다.

반면에, 아키텍처는 기능을 잘 변경할 수 있는가를 말합니다.

기능이 잘 동작하는 소프트웨어는 누구나 만들 수 있습니다.

하지만, 기능을 잘 바꿀 수 있는 소프트웨어를 만드는 것은 그렇지 않습니다.

소프트웨어가 정말로 soft 하다면 기능을 추가, 변경하는데에 있어 편해야할 것입니다.

책에서는 클린한 아키텍처를 통해 해당 문제를 해결할 수 있다고 보고 있습니다.

구조적 프로그래밍

구조적 프로그래밍에서 소프트웨어는 증명하는 것이라고 정의하고 있습니다.

과학에서의 증명은 해당 이론이 맞는가보다는 틀리지 않았는가에 집중하여 증명합니다.

소프트웨어에서 증명은 테스트를 통하여 수행할 수 있습니다.

테스트는 소프트웨어가 잘 수행되는지보다 틀리게 수행하지 않았는지에 대해서 증명할 수 있습니다.

때문에 빌드를 결정할 때, 테스트 통과 유무를 적극적으로 활용해서 잘 돌아가는지를 보장할 수 있습니다.

객체 지향 프로그래밍

객체 지향에서 중요한 것은 다형성이라고 말합니다.

다형성을 이용해서 의존성을 절대적으로 제어할 수 있음을 강조하고 있습니다.

그로 인해, 고수준의 추상 모듈과 저수준의 구체 모듈에 대해 독립성을 보장할 수 있습니다.

그래서 로버트 마틴은 모듈을 플러그인으로 만드는 것을 추천하고 있습니다.

함수형 프로그래밍

함수형 프로그래밍에서 중요한 개념은 불변성입니다.

변수가 변하지 않는다면 경합 조건, 교착상태, 동시 업데이트와 같은 동시성 문제에 대해 상관하지 않아도 됩니다.

다만 변경된 사항들을 모두 저장하는 트랜잭션 공간이 추가적으로 필요합니다.

여담으로 리액트에서 가변 컴포넌트와 불변 컴포넌트를 분리 할 수 있는 패턴으로

Presentational - Container 패턴이 있습니다.

Container 컴포넌트에서는 사이드 이펙트를 통해서 외부의 값을 가져오고

Presentational 컴포넌트에서는 props 만을 이용해서 화면을 렌더링 하는 기능을 합니다.

이는 각각 가변 컴포넌트, 불변 컴포넌트에 대응됩니다.

컴포넌트

코드의 응집도는 높이고 결합도는 낮춰라

응집도를 높이는 방법으로

재사용/릴리스 등가 원칙, 공통 폐쇄 원칙, 공통 재사용 원칙이 있고,

결합도를 낮추는 방법으로는

의존성 비순환 원칙, 하향식 설계, 안정된 의존성 원칙, 안정된 추상화 원칙이 있습니다.

아키텍처

클린한 아키텍처 안에 담긴 소프트웨어는 쉽게 개발, 배포, 운영, 유지보수할 수 있습니다.

이러한 일을 용이하게 만들기 위해서는 가능한 많은 선택지를 오래 남겨두는 전략을 따라야한다고 설명하고 있습니다.

선택지란 세부사항을 선택하는 것을 의미합니다.

프레임워크, 데이터 베이스 같이 외부 인터페이스에 속하는 것들이 세부사항이 될 수 있습니다.

예를 들어,

  1. mysql을 쓸 것인지 mongodb를 쓸 것인지

  2. express를 쓸 것인지 nest를 쓸 것인지

같은 선택지들이 있을 수 있습니다.

이러한 외부 인터페이스는 쉽게 대체 가능해야하며 선택하지 않음으로 그렇게 만들 수 있다라고 말합니다.

프로젝트를 셋팅할 때, 프레임워크를 선택하고, 데이터베이스를 선택했던 과정들이

사실은 셋팅 단계 때 하는 것이 아니다라는 사실을 일깨워주고 있습니다.

반대로 먼저 선택해야하는 것은 업무 규칙에 속하는 엔티티와 유스케이스 입니다.

엔티티의 예로는 송금하기, 인출하기, 문자 보내기와 같은 단순한 규칙들이 있습니다.

유스케이스의 예로는

(잔액이 0원 이상일 때) 송금하기,

(잔액이 0원 이상일 때) 인출하기,

(보내는 사람을 선택해서) 문자 보내기와 같이 애플리케이션에 특화된 업무규칙이 있습니다.

엔티티와 유스케이스는 어느 특정 패러다임 프로그래밍 기법을 사용할 필요가 없습니다.

엔티티는 클래스가 될 수도 있고 함수가 될 수 있습니다.

이렇게 업무 규칙들을 먼저 작성하는 것은

웹을 통해 서비스 되는지 모바일로 서비스 되는지

인메모리 디비를 사용하는지 외부 디비를 사용하는지를 모르고 만들어야 클린한 아키텍처를 유지할 수 있다라고 정의합니다.

목킹용 데이터베이스를 만들때 인메모리 방식으로 만드는 것도 이와 관련이 있을 것 같습니다.

빠져 있는 장

이 장은 시몬 브라운이라는 사람이 기고한 장으로 패키지 구성 방식에 대해 설명하고 있습니다.

4가지 구성 방식에 대해 이야기하고 있는데

  1. 계층 기반 패키지

  2. 기능 기반 패키지

  3. 포트와 어댑터

  4. 컴포넌트 기반 패키지

들을 이야기하고 있습니다.

계층 기반 패키지는 디렉토리를 계층별로 나눕니다.

(폴더 구조는 타입스크립트 기준입니다!)

\controller
ㄴ user.controller.ts
ㄴ product.controller.ts
ㄴ order.controller.ts
\service
ㄴ user.service.ts
ㄴ product.service.ts
ㄴ order.service.ts
\repository
ㄴ user.repository.ts
ㄴ product.repository.ts
ㄴ order.repository.ts

해당 폴더 구조는 프로젝트를 단순하게 시작할 수 있는 아키텍처 입니다.

프로젝트를 처음 맞닥드릴때 controller와 service와 repository가 먼저 보일것입니다.

하지만, 이러한 구조는 업무 도메인에 대해서는 아무것도 말해주고 있지 않아 어떤 소프트웨어인지 한눈에 파악하기 힘듭니다.

엉클 밥은 소프트웨어의 구조를 바라볼 때 어떤 소프트웨어인지 말하는 것이 중요하다 말합니다.

우리가 도서관 설계도를 보면 도서관이라고 판단하고, 가정집 설계도를 보면 가정집이라고 판단 하는 것과 똑같다라고 할 수 있습니다.

다음으로 기능 기반 패키지입니다.

\user
ㄴ user.controller.ts
ㄴ user.service.ts
ㄴ user.repository.ts
\product
ㄴ product.controller.ts
ㄴ product.service.ts
ㄴ product.repository.ts
\order
ㄴ order.controller.ts
ㄴ order.service.ts
ㄴ order.repository.ts

기능 기반 패키지는 연관된 기능, 도메인을 하나의 디렉토리로 묶습니다.

그로 인해, 업무 도메인을 한눈에 볼 수 있게 해주며

특정 도메인의 수정이 필요할 때 이곳저곳(계층)을 탐색할 필요가 없습니다.

포트와 어댑터는 다음과 같습니다.

\web
ㄴ user.controller.ts
ㄴ product.controller.ts
ㄴ order.controller.ts
\domain
ㄴ user.service.ts
ㄴ product.service.ts
ㄴ order.service.ts
\database
ㄴ user.repository.ts
ㄴ product.repository.ts
ㄴ order.repository.ts

web 과 database 는 외부 인프라이기 때문에 계층을 분리하고

domain 에 비즈니스 로직을 넣습니다.

domain 파일들에는 각각 User, Product, Order 라는 인터페이스가 구현되어 있으며

이는 유비쿼터스 도메인 언어 관점에서 기술하기 때문에 그렇습니다.

마지막으로, 컴포넌트 기반 패키지입니다.

\web
ㄴ user.express.ts
ㄴ product.grqphql.ts
ㄴ order.fastify.ts
\user
ㄴ user.component.ts
ㄴ user.repository.ts
\product
ㄴ product.component.ts
ㄴ product.repository.ts
\order
ㄴ order.component.ts
ㄴ order.repository.ts

컴포넌트 기반 패키지는 기능 기반 패키지에서 controller 부분만 떨어져 나왔습니다.

파일명을 자세히 보시면 web 디렉토리내의 중간 이름들이 다 다른데

이는 component 는 web이 무엇으로 구성되어 있던 전혀 상관 안 하고 개발을 하는 것을 의미합니다.

그래서 web으로 전달되는 무언가를 어떻게 구현할지는 controller 에서만 선택해서 진행할 수 있습니다.

user 서비스는 주로 로그인과 회원가입과 같은 폼 형식의 데이터를 처리하는 것이니 express를 사용할 수 있고

product 서비스는 주로 list 혹은 detail 데이터를 들고오는 쿼리 기반 데이터로 들고오기 때문에 graphql을 사용할 수 있고

order 서비스는 사용자의 결제를 빠르게 유도해야하니 fastify 를 사용할 수 있습니다.

이렇듯 각 용도에 맞춰 web api 를 결정할 수 있으며 컴포넌트는 어떠한 web api 와도 호환이 가능합니다.

결론

프레임워크와 데이터베이스를 가능한 선택하지말고 구현해라

이는 핵심적인 비즈니스 로직을 먼저 작성하라는 말이며

비즈니스 로직을 먼저 작성하는 습관을 들이면 프레임 워크와 데이터베이스를 생각하지 않고

개발할테니 자연스럽게 클린한 아키텍처가 형성됩니다.

이 책을 지금와서야 읽은게 다행이라고도 생각되면 항상 느끼던 패키지 구성(폴더 스트럭처)의 어려움을

조금이나마 해소할 수 있었습니다.


질문 사항이나 틀린 사항 있을 시 피드백 주시면 감사하겠습니다.