티스토리 뷰

클린 아키텍처

xephysis 2021. 6. 29. 12:33

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=202322454 

 

클린 아키텍처

소프트웨어 아키텍처의 보편 원칙을 적용하면 소프트웨어 수명 전반에서 개발자 생산성을 획기적으로 끌어올릴 수 있다. 《클린 코드》와 《클린 코더》의 저자이자 전설적인 소프트웨어 장인

www.aladin.co.kr

나온지는 조금 된 책이다.

하루에 한두 장씩 읽고 나서 느낌을 정리한다.

(분량은 이틀~사흘 정도면 다 읽을 것 같지만 이번엔 좀 곱씹으면서 읽고 싶다, 통독 말고 정독으로)

 

이 글의 목적은 책의 내용을 요약하는 것이 아니다.

(이 책을 사서 읽었으면 하는 바람이다.)

 

나름대로 이해하고 공감하고 생각을 정리하는 것이 목적이다.

당연히 '오해', '곡해' 하는 것도 있으리라.

(== 내 멋대로 해석하고 이해할 거다.)

이런 책은 스터디로 토론하면서 진행하면서 읽으면 좋겠지만 상황이 상황이니 이런 형태로나마 정리해보려고 한다.

 

 

아키텍처

아키텍처는 중요하다. 비용 때문이다.

요구사항은 계속 늘어난다. 코드는 변해야 한다.

아키텍처가 유연하지 못한다면 갈수록 기능 한두 개를 반영하기 위해 몇 시간~며칠이 걸릴 수도 있다.

결국 돈(비용)과 관련된 문제이다.

 

행위(기능)와 구조(아키텍처)

행위보다 구조가 더 중요하다고 한다.

행위에 문제가 있으면 고쳐 쓸 수 있지만, 구조에 문제가 있으면 고칠 수가 없다는 맥락이다.

어느 정도는 동의한다. 

비유는 알겠고, 왜 중요한지는 정말 공감하는데 '행위에 문제가 있으면' 안된다고 생각한다.

(완벽할 수는 없겠지만 의도한 대로 동작해야 한다.)

 

패러다임

구조적 프로그래밍, 객체지향 프로그래밍, 함수형 프로그래밍

직관적일 수는 있지만 어려운 개념들이라고 생각한다.

 

구조적 프로그래밍에서는 순차, 분기, 반복이 중요하다고 생각한다 (개인 의견)

-> 조금 더 읽다 보니 문제(기능)를 지속해서 분해해 나가는 구조인 것 같다.

-> “Program testing can be used to show the presence of bugs, but never to show their absence!”

-> 결국 goto 가 문제였다

 

객체지향 프로그래밍에서는 메시지 디스패치가 중요하다고 생각한다 (역시 개인 의견)

-> 객체지향 4가지 특성만으로는 부족한 걸까?

-> OOP에서 캡슐화 안 지켜지고 있고, C로 상속(비슷한 건) 얻어낼 수 있다.

-> 다형성은 비록 함수 포인터의 응용이지만 OOP의 핵심이라고 말하는 것 같다.

(궁금해서 찾아봤지만 역시나 논란이... https://forums.codeguru.com/showthread.php?392878-Function-Pointer-vs-Polymorphism )

-> 다형성을 통해 IoC를 얻어낼 수 있다. (https://stackoverflow.com/a/21177596), (https://ko.wikipedia.org/wiki/%EC%A0%9C%EC%96%B4_%EB%B0%98%EC%A0%84)

-> 잘못 이해한 것 같다. DI(Dependency Inversion)를 얻어내는 게 맞다. DI 개념에 IoC가 포함되어 있다고 한다. (IoC라는 말을 쓰지 말고 Dependency Injection이라고 해야 한다는 주장도 있고 IoC를 하기 위한 방법이 Dependency Injection이라는 말도 있고)

-> 다른 의견은 없나 해서 파보니 재미있는 소개가 있다. 객체지향의 핵심은 'IoC를 통해 상위 레벨의 모듈을 하위 레벨의 모듈로부터 보호하는 것' (https://www.youtube.com/watch?v=mI1PsrgogCw&ab_channel=%EB%B0%B1%EB%AA%85%EC%84%9D) (SOLID와 같은 맥락인 것 같고 가장 와닿는 말씀, 물고 늘어지자면 IoC보단 오히려 DI가 더 적합하겠지만)

 

함수형 프로그래밍은 아직까지도 잘 모르겠다. 사이드 이펙트가 없는 점(불변성)이 중요한 부분일까? 

-> 역시 이뮤터블... 

-> CAS는 다시 파봐야겠다. 

-> 이게 이벤트 소싱까지 연결되네...?

 

패러다임이 '무엇을 할지' 보다 '무엇을 하지 말아야 할지'를 결정한다고 한다.

 

설계 원칙

SOLID 이야기다. 

S - 책임을 나타내지만 핵심은 변경에 관한 것이라고 생각한다.

-> 사이드 이펙트 사례, 코드 컨플릭트 사례

-> 이걸 데이터와 메서드를 분리하는 건 공감하는데 파사드로 푸는 게 맞나?

-> 컴포짓으로 구체적인 행위는 위임하면서, 푸는 사례도 있는 것 같다.

-> 내용을 출력하기 위해 구체적인 동작을 (JSON이던 XML이던) 위임하는 사례가 적합해 보인다. (https://javacan.tistory.com/entry/OO-Basic-5-Principles)

-> 이 내용도 깊게 파고들자면 끝이 없을 것 같다. GRASP는 좀 천천히 곱씹어 보는 걸로

 

O - 확장을 표현하지만 결국 이것도 변경에 관한 문제라고 생각한다.

-> 확장을 할 때 기존에 작업한 사항에 변경이 있어서는 안 된다.

-> 갑자기 든 생각인데, 방법론적이라기보다는 철학에 가까운 것 같다.

-> '책임'을 잘 나눠놓으면 의존성을 최소화, 체계화할 수 있고 변경에 강한 게 아닐까

-> interactor라는 개념이 나와서 조금 찾아보고 있는데 잘 나오진 않는다. 기존에 알고 있던 개념으로 도메인 로직을 담당하거나 service레이어쯤으로 생각된다.

 

L - 상위 타입을 하위 타입으로 동작이 보장되어야 한다는 건, 결국 구체적인 동작이 바뀌어도 규약을 지켜야 한다는 거니 이것도 변경에 관한 문제일 것 같다.

-> 뻔하다면 뻔하고 유명하다면 유명한 직사각형을 정사각형이 상속해서는 안 되는 문제...(정사각형을 직사각형으로 업 캐스팅 해 놓은 뒤 가로세로 따로 저장하고 너비를 구하면 잘못된 너비가 출력된다는 그 문제)

-> 인터페이스(그 인터페이스와 통신 인터페이스 혼용하고 있긴 한데)를 잘 정의해야 한다고 생각한다. 

-> LSP 가 위배되기 시작하면 if-else의 향연이 시작된다. (+instanceof, typeof까지)

 

I - SRP 하고 중복되는 개념이 아닐까 하고 생각한다. (안 쓰는 인터페이스에 의존하지 말아야 하는 거니) 변경.... 은 아니지만 결국 변경하고 연관되어 있지 않을까?

-> 자신이 사용하지 않는 메서드를 가진 인터페이스에 의존해서는 안된다, 즉 인터페이스를 잘 분리해야 한다.

-> 언어에 따라 다를 수도 있긴 한데... (언어 문제는 또 아니라고 한다. 그리고 동적 타입 언어 별로 안 좋아한다 타입 안정성이 너무 떨어지고 나중에 코드 보는 사람이 싫어하게 될 짓거리를 할 확률이 높아진다.)(비문이다.)

-> 불필요한 의존성이 많아진다면 배포나 이런 관점에서 문제가 발생할 여지가 많다고도 한다. (매우 많이 저지르고, 당해봐서...)

 

D - 추상화의 의존 관계 설정 관련된 내용이지만, 마찬가지로 변경에 관한 문제라고 생각한다.

-> 추상에만 의존하고 구체에 의존하지 않으면 어떻게 코드를 짜라는 건가?라는 생각을 자주 하게 된다. (Collection까지만 쓰라는 건가 List도 써도 되는 건가? 맞다 이건 그냥 말꼬리 잡기다.)

-> 역시 작가도 비슷한 논조로 시작한다. 잘 안 바뀌는 추상화에 의존하고, 잘 바뀔 수도 있는 구체화는 의존하지 말라는 소리다.

-> 역시 상속이나 override 함부로 하지 말라는 말도 나온다. (변동성이 큰 무언가에 의존하려면 팩토리 쓰라고도)

-> 간략하게 축소화해서 받아들이면 저장소가 바뀔 수도 있고, 표현방법이 바뀔 수도 있으니 이런 부분을 추상화한다고 생각해도 되려나 (회원, 결제도 마찬가지고)

 

'SOLID는 모두 변경에 유연하게 대응하기 위한 게 아닐까?'라는 생각이 드니 자꾸 그런 시선으로만 바라보게 된다.

 

 

컴포넌트

독립적으로 배포 가능한 단위

로더, 링커, 그리고 링킹 로더 이야기네 (순서는 조금 미묘한데)

정적 링킹은 시간도 오래 걸리고 비효율적이다

동적 링킹 https://wiki.kldp.org/Translations/html/LinkerLoader-KLDP/dynamicliking.html

오랜만에 보니 새롭네...

https://www.geeksforgeeks.org/difference-between-linker-and-loader/

https://hcn1519.github.io/articles/2019-09/dynamic_library

https://www.lesstif.com/software-architect/shared-library-linker-loader-12943542.html

 

컴포넌트 응집도

REP 재사용/릴리즈 등가 원칙 (묶어라)

-> 재사용 단위는 릴리즈 단위와 같다.

-> https://stackoverflow.com/questions/63142/the-reuse-release-equivalence-principle-rep

-> 재미있는 관점인데 변경을 추적하기 쉽다는 맥락도 있다

-> https://dev.to/naomidennis/package-cohesion-reuse-release-equivalence-principle-3d28

-> 또 다른 해석, 패키지안에 있는 모든 클래스들은 반드시 재활용 가능해야 하며, 모든 패키지들은 릴리즈가 관리되어야 한다. 라며 java.io 패키지를 예로 듬

-> 다시 봐도 막연한 개념인데...

-> 막연하다기보다는 너무 당연한 소리에 가까운 것 같다. apache commons 가 버전 관리가 안되고 있다면 과연 쓸까? 어디서 어떻게 뭐가 바뀌었는지 모르는데? (물론 하위 호환을 어느 정도는 잘 지켜주겠지만)

 

CCP 공통 폐쇄 원칙 (묶어야 하는 것들을 묶고 나눠야 하는 것들을 나눠라)

-> 같은 이유로 같은 시점에 변경되는 것들만 묶고, 다른 이유로 다른 시점에 변경되는 것들은 분리하라

-> OCP, SRP 확장 개념이라고 한다.

-> 그렇게 까지 안 와닿지는 않고

-> https://medium.com/@andrewMacmurray/package-principles-850d89da5e5b

-> 같은 이유로 변경되는 것들을 한 패키지에 몰아넣어서 다른 곳에서의 의존성을 최소화할 수 있다... 정도로 이해

-> 이것도 apache commons로 예를 들면 좀 와닿는다. https://commons.apache.org/ 저기에 있는 각 모듈들이 어중간하게 섞여 있다고 생각하면 끔찍하다.

 

CRP 공통 재사용 원칙 (나눠라)

-> 컴포넌트 사용자들은 필요하지 않은 것에 의존하게 강요하지 마라

-> 약간 다른 맥락으로 공감, 묶어도 될 것과 묶지 않아도 될 것들을 구분해야 한다. 필요하지도 않은데 의존하면 묶지 않아도 될 것들도 묶인다.

-> ISP 확장 개념이라고 한다.

-> https://wiki.c2.com/?CommonReusePrinciple

 

내용만 보면 3가지 원칙이 약간씩 상충한다고는 한다.

 

여러 가지 이유로... 다른 성격의 컴포넌트와 메서드들을 묶는 경우가 있는데 분리하는 게 맞는 것 같다. 패키지도 같은 맥락이라고 생각한다. monolith도 잘 구성되어 있으면 modulith? msa로 언제든지 전환이 가능하.... 겠지?

 

너무 작게 말아 놓으면 배포 빈도가 잦아지고, 너무 크게 말아 놓으면 그건 뭐.... 하나 배포될 때마다 연관되는 컴포넌트도 전부 포함해서 재배포할지 말지 결정을 해야 된다. 거기다가 기능 추가하거나 버그 잡으려고 한다면 대체 이 망망대해에서 뭘 어디를 봐야 하는 것인가 하면서 코드(정확히는 구조) 분석부터 시작해야 한다.

그리고 아무리 잘해놔도 지금 정답이 1년 뒤, N 년 뒤에 그대로도 먹힌다는 보장이 없다. (애초에 처음부터 잘하기도 어렵고....) 언제든지 잘 뜯어낼 수 있는 구조를 고민하면서 짜는 게 맞을 것 같기도 하고...

 

https://en.wikipedia.org/wiki/Package_principles

위키를 보니 아예 패키지 레이어로 설명을 하는 것 같다.

차라리 java 기준으로 패키지나 모듈의 응집도라고 생각하면 조금 더 와닿는 것 같다.

 

컴포넌트 결합

ADP: 의존성 비순환 원칙

-> 당연히 사이클 생기면 피곤하다... 정확한 내용이 나온다. 순환이 생기면 버전을 관리하는 의미 없이 모두가 같은 버전을 사용하게 되고 변경이 필요 없는데도 동시에 릴리즈가 진행되어야만 한다. (빌드 순서도 중요해지면서, 분리도 어려워지고)

-> 주 단위 빌드는 반대.... 배포 주기는 짧을수록 좋다고 생각하고, CI/CD가 되어야 한다. (어렵다)

-> 컴포넌트를 개별 담당자 또는 단일 개발팀이 책임질 수 있는 단위로 풀 수 있다니.... 정말 꿈같은 이야기다 (왜 우리는 모두가 프로젝트를 하나 이상 통째로 맡아야만 하는가...)

-> 역시 컴포넌트는 계속 바뀌어야만 한다. 왜냐? 요구사항은 계속 바뀌니까

-> 그리고 바뀌기 쉽게 잘 쪼개져 있어야 한다.

-> 이러니 저러니 해도 일단 해보고.... 리팩터링 해야 한다고 생각한다.

 

SDP: 안정된 의존성 원칙

-> DIP랑 같은 맥락인 것 같은데, 잘 변하지 않는 추상화에 의존하고 구체화에 의존하지 말라는?

-> 단순 코딩뿐만 아니라 전체 시스템에도 해당하는 이야기다. 다른 쪽에서 우리 시스템에 빨대를 꽂는다? 그 빨대 없애기 어렵다...

-> 의존성과 안정성은 비대칭적 개념인 듯?

-> 그렇다고 안정성을 높이기 위해 모든 의존을 끊어버린다? 그건 좀 아닌 것 같고 역시나 DIP로 추상 컴포넌트/인터페이스에 의존하는 방법을 택하는 것 같다.

 

SAP: 안정된 추상화 원칙

-> 필요 이상 추상화하지 마라, 안정된 컴포넌트는 추상 컴포넌트 이어야 한다..? 이해가 부족해서 그런지 잘 안 와닿는데...

-> 안정된 컴포넌트는 추상 클래스, 인터페이스여야 하고 불안정한 컴포넌트는 콘크리트 클래스여야 한다고 하는데 컴포넌트가 그런 단위로 쪼개지는 게 맞나? 여기도 패 지키라고 이해해야 하는 건가

-> 대부분 고통의 구역으로 위치하지 않나...? 이건 반성해야 하는 건인가

-> 쓸모없는 구역이야 뭐... 저렇게 추상화 잔뜩 해 놔봐야 아무데서도 참조하지 않으면 문서나 다름없어 보인다.

-> 음... 이런 식으로 추상화와 안정성을 계산하는 게 보편적인 건가?  느낌과 감으로 해와 버릇한 것 같은데 이것도 반성해야 하는 건인가

 

https://woowabros.github.io/study/2018/03/05/sdp-sap.html

역시나 재미있는 아티클이 있다. 저자분의 생각에 매우 공감하는 바가 있다.

2021.05.15 - [개발관련/이론] - 클린 아키텍처의 추천사

 

아키텍처란?

처음부터 가장 공감 가는 문구들이 있다.

'문제를 겪어보지 않으면 제대로 수행할 수 없다.'

'가능한 많은 선택지를, 오래 남겨두어야 한다.'

 

'팀이 개발자 다섯 명으로 구성될 정도로 작다면' 작다 작은 거다.... 한두 명이 할 일이 아니다 이런 상황에서는 모놀리스가 적합할 수도 있다.(고 생각한다) :(

msa 가 만능은 아니다. 오히려 초기라면 배포하기 어렵다. (특히 로컬에서 개발하는 것도)

그러면 어째야 할까... 아직도 잘 모르겠다. 모놀리스로 초반에 치고 달리다가 어느 시점부터는 잘 쪼개야 하는 건가? 그 리소스는 감당 가능한 것일까?

 

유지보수에서 가장 큰 비용은 탐사와 위험부담이라고 한다. 격렬히 공감한다.

 

정책을 결정하고 세부사항은 최대한 많은 선택지를 오래 남겨두는 게 좋다고 한다. 맞는 말이다. 저장소를 파일로 하던, RDBMS로 하던, NoSQL로 하던, API로 하던, 그런 세부 사항들은 최대한 늦게까지 남겨 놓는 게 좋다고 생각한다.

결국 헥사고날 아키텍처로 가야 하는 건가 하는 생각이 든다.

https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture/

 

독립성

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

콘웨이의 법칙 https://johngrib.github.io/wiki/Conway-s-law/

 

'대부분의 경우 우리는 모든 유스 케이스를 알 수는 없으며, 운영하는 데 따르는 제약사항, 팀 구조, 배포 요구사항도 알지 못하기 때문이다.' 클린 아키텍처 P.159. ch16. 독립성

첨언하자면 위의 팩터들은 계속 바뀐다. 유스 케이스던, 팀 구조던 그러니 선택지들을 좁혀버리면 안 된다.

 

케이크를 가로로 자를 것인가 세로로 자를 것인가 계속 고민하는 사항인데 저자는 가로로 자르는 걸 택하는 것 같다.

 

우발적 중복... 몇 년 전에 배치를 짜면서 비슷한 콘텍스트라고 단정 짓고 한 프로젝트로 몰아넣은 일이 있다. 그러지 말았어야 했다. 비슷한 성격과 비슷한 동작을 하는 배치는 맞지만 엄연히 다른 행위였다. 설정으로 동작을 분리하였지만 이제라도 나눠 두어야 할까... 

 

msa가 만능은 아니겠지만 현시점에서는 가장 인기 있는 방법이 될 것이다. 그런데 서비스 간 경계를 처리하는데 드는 비용이 합당하고 감당할만한 것일까? 아니면 단지 선택지만 열어두는 방법이 옳은 것일까 

https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture/

 

지속 가능한 소프트웨어 설계 패턴: 포트와 어댑터 아키텍처 적용하기 - LINE ENGINEERING

육각형 설계(Hexagonal Architecture)로 더 잘 알려져 있는 포트와 어댑터 설계(Ports and Adapters Architecture)는, 인터페이스나 기반 요소(infrastructure)의 변경에 영향을 받지 않는 핵심 코드를 만들고 이를 견

engineering.linecorp.com

https://woowabros.github.io/study/2019/07/01/multi-module.html

https://www.slideshare.net/arawnkr/ss-195979955

 

잘 키운 모노리스 하나 열 마이크로서비스 안 부럽다

마이크로서비스 스타일로 만들어진 시스템을 모노리틱 스타일로 이관한 사례와 함께 스프링을 이용해 모듈형 모노리스(modular monoliths)를 만든 경험을 바탕으로 모노리틱/마이크로서비스 보다

www.slideshare.net

경계: 선 긋기

너무 이른 시기에 내려진 결정으로 인해 발생하는 '결합' (결함이 아니다.)이 비용을 늘린다.

 

속성 하나를 추가하기 위해 여러 개 클래스에 영향을 받고, 통신방법과 처리 방법에 영향을 받는 것을 예제로 드는데... 어차피 이건 이른 시기에 결정했다고 발생하는 문제일까? (서버팜 구조를 상정했지만 그럴 일이 없었으니 그게 문제라고 하는 것 같다.)

 

그다음 사례는 엔터프라이즈 패턴을 사용하는 것으로 보이는데, 이건 스키마랑 도메인 로직을 잘못 짠 사례인 것 같은데

 

입출력이나 저장소는 중요한 사항이 아니라고 한다. 포트 어댑터 패턴이랑 똑같네...

 

경계 해부학

경계를 어떻게 나눌 것인가?

동일 컴포넌트에서 함수 호출 레벨로 나눌지

라이브러리화 시켜서 라이브러리의 함수를 호출하는 방식으로 나눌지

로컬 프로세스를 띄워서 IPC 방식으로 호출할지

리모트 서비스로 분리할지

크게 와닿는 내용은 아니지만, 사실 엄청 중요한 내용이기도 하다.

왜냐하면 모듈 구성은 매번 마음에 안 들니.... 

 

정책과 수준

소프트웨어 시스템은 정책을 기술한 게 다라고 한다.

컴포넌트들을 DAG로 표현할 수 있는데, node는 정책을 담당하는 컴포넌트, edge는 의존성 (근데 데이터도 어느 정도 컴포넌트 다이어그램으로 표현 가능할 텐데)

역시나 여기서도 잘 변할만한 입출력을 저수준의 컴포넌트 잘 변하지 않는 동작을 고수준의 컴포넌트로 정의하고 의존성은 저수준에서 고수준에 의존

이러니 저러니 해도 결국 핵사고 날 아키텍처, 포트-어댑터 패턴이 명시적으로 나오기 전까지 계속 이 이야기가 나올 것 같다.

 

업무 규칙

업무 규칙을 정의하고 있는데, 결국 엔티티로 전개된다. (로직+데이터)

엔티티가 데이터베이스, 사용자 인터페이스, 서드파티 프레임워크에 대한 고려사항으로 오염되면 안 된다....라고는 하는데 정말 그렇게 엄밀하게 구분이 가능한 걸까(annotation으로 메타 데이터가 엄청 붙는데....)

유스 케이스에 대한 정의 

엔티티랑 다른 점은 엔티티는 핵심 업무 규칙에 대한 정의를 나타내고 유스 케이스는 애플리케이션 레벨을 상정하는 듯 (더 상세하니까 엔티티보다 추상화 레벨이 낮다고 표현하는 건가)

마찬가지로 유스 케이스에서도 저수준 정책에 대해서는 기술하지 않으며 사용자와 엔티티 사이의 상호작용에 대한 정의인 듯

그게 되려면 엔티티에서도 저수준 정책(입출력 등)에 관여하지 않아야 한다가 성립.

소리치는 아키텍처

아키텍처는 세부 구현이나 프레임워크가 무엇인지 구구절절하게 설명해서는 안되고, 딱 보고 이게 '무엇이다'를 나타내야 한다.

아키텍처는 유즈 케이스에 대해 소리쳐야 한다.

(구구절절한 세부사항이나 프레임워크는 역시나 언제든지 바뀔 수 있고 지엽적인 사항이라고 한다.)

동작하는 시스템이 콘솔 기반 애플리케이션인지, 웹인지, 웹앱인지, 앱인지 이런 데에 집중해서는 안된다고 한다.

위 사항에 대해서는 개인적으로는 반쯤만 공감, '아무것도 몰라야 한다.'라는 접근은 위험하고 '여러 가지 형태로 서비스될 수 있어야 한다'를 전제 조건으로 깔아야 하는 게 아닐까 생각한다.

프레임워크도 비슷한 시선인데, 분명 프레임워크는 세부사항이지만, 프레임워크에 따라 영향받는 부분이 없다 라고 단정 짓기는 어렵지 않을까 생각한다.

그럼에도 불구하고 강하게 공감하는 부분은 '프레임워크, 저장소 없이도 핵심 유즈 케이스에 대해 테스트를 돌릴 수 있어야 한다'이다. (뭐... 이것도 통합 테스트라면 또 이야기가 달라지겠지만)

 

클린 아키텍처

책 제목과 챕터 제목이 동일한 장이다.

역시나 궁금했던 헥사고날 아키텍처, 포트-어뎁터 패턴이 등장한다.

이러니 저러니 해도 중요한 건 관심사항의 분리인 것 같다.

OSI 7 레이어와 그 궤를 같이하는 것이겠지. (각자 책임져야 할 부분만 책임지고, 관심 가져야 할 부분만 관심을 가진다.)

다만, 헥사고날 아키텍처에서 가장 중요한 건 의존성인 것 같다. (밖에서 안을 의존한다, 그게 코드이던 콘텍스트이던, 로직이던)

개인적으로 궁금한 건 이런 구조가 무 자르듯 이렇게 싹둑싹둑 구분이 되는 걸까?

아니면 고민과 노력이 조금 부족했던 걸까?

단순히 spring-webmvc 기준으로 생각하면 프레젠터-> 컨트롤러-> 서비스-> 리파지토리로 원 밖에서 시작해서 안으로 들어왔다가 나가는 구조인데, 이걸 인터페이스로 의존성을 역전시키는 게 아직까지도 잘 와닿지만은 않는다.

조금 더 경험과 삽질이 필요하다.

 

프레젠터와 험블 객체

험블 객체라는 단어를 처음 들어봤는데 테스트 도메인에서 사용되고, 프레젠터와 뷰를 나누는 기준? 인 것 같다.

https://martinfowler.com/bliki/HumbleObject.html

간략하게 이해한 바로는 테스트가 가능한 부분과 테스트하기 어려운 부분을 나누고, 테스트가 어려운 행위를 험블 객체로 넘긴 다음, 이런 부분을 프레젠터가 아닌 뷰로써 관리한다. 이런 느낌 같은 느낌으로 이해했다.

즉, 데이터를 직접 관리하는 부분을 프레젠터로 관리하면서 테스트를 적용하고,  UI영역에 해당하는 테스트하기 어려운 부분을 뷰로 관리한다. 이런 개념인 것 같다.

프레젠터는 controller, 뷰는 정말 뷰.... 같은 느낌이려나 (물론 프런트엔드 프레임워크 위에서도 이런 구분이 그대로 녹아들 것 같다.)

 

이 장에서는 결국 이런 식으로 아키텍처를 위해 영역을 나누는 걸 설명하고 있다.

 

데이터를 연동하는 부분은 실제로 드라이버와 접촉하는 부분과 모킹 할 수 있는 부분이 나뉜다.

이것도 테스트가 가능한 부분과 테스트하기가 어려운 부분을 나눠서 테스트하기 어려운 부분을 험블 객체로 관리해야 한다고는 하는데, 

통합 테스트 성격에다가 테스트 DB 붙이면 안 되는 건 아니지만... 그런 걸 말하고 싶은 건 아닌 것 같다.

 

그다음 ORM의 경우는 몇 번 읽어봐도 잘 이해가 안 가는데... ORM이라는 건 없다고 하면서, 논리를 전개하는데 잘 와닿지는 않는다.

(오브젝트는 행위를 담당하는데 ORM 은 행위라기보다는 결국 오브젝트들에 데이터를 매핑하므로 오브젝트 릴레이션 매퍼라는 개념 자체가 잘못되었다고, 데이터 매퍼가 맞다고는 하는데....)

 

아니 그러면 ORM 영역에 대한 테스트는 어떻게 하는 게 맞는 건가, 그냥 모킹 하면서 유닛 테스트하고 테스트 DB 연동하면서 통합 테스트하면 되는 거 아닌가 싶기도 하는데,

이렇게 '모킹 하면서 유닛 테스트' 하는 이외의 동작 즉 통합 테스트로만 검증 가능한 부분을 험블 객체로 관리해야 하는 건가? 

(쓰면서도 이게 말이 되는 말인가 계속 확인하게 된다.)

 

부분적 경계

경계를 완벽하게 나눠두는 건 좋지만 그만한 리소스가 당장 투입할만한지는 여러모로 검토가 필요하다. (자체 해석)

'어쩌면 필요할지도...' 하는 부분들에 대해 대비 책 정도를 소개하는 챕터이다.

개인적인 경험으로 필요할지도 하는 부분들은 필요하게 된다. 아닐 수도 있고 :) 

 

분리하기 직전 단계로써 멈춰두는 방법이나, IoC에 의존하는 방법(전략 패턴 사용), 파사드 패턴을 사용하는 방법 등이 있다고 한다.

단편적인 지식으로는 파사드 패턴이 모놀리틱에서 MSA로 가기에 수월하지 않나 정도로 생각한다. (MSA를 매번 고민하지만 매번 벽을 마주쳤던 경험에서 이 선택지가 적합할지는 잘 모르겠다. 여기도 정답은 없겠지)

 

계층과 경계

플계층은 대체 뭐지, 아이 player 를 플계층이라고 번역해버린 거야? 너무하네

(플레이어로 번역했다가 이후에 레이어를 일괄로 계층으로 치환하면서 이런 사달이 난 것 같긴 한데...)

UI, 로직, 저장소를 잘 분리하라는 내용일 것 같은데, 이런 게 보이니 집중이 떨어진다.

결국 UI도 추상화, 저장소도 추상화해놓고 비즈니스 로직에서 이라는 상위 개념에서 구체화된 세부사항에 의존하지 말라는

소리다. 

 

메인 컴포넌트

엔트리 포인트이며, 가장 세부적인 사항....이라는 설명이 적합하다고 생각한다.

핵심 로직을 호출해주거나 프레임워크가 적용되는 부분이기도 하고 가장 변화에 민감한 부분이 아닐까 생각한다.

메인을 아키텍처 밖에서 관리하라... 정도가 얻어갈 만한 사항

 

'크고 작은 모든' 서비스들

MSA에 대한 고찰인 것으로 보인다.

결국 서비스 간 호출이라는 건 함수로 호출하던 비싸게 API로 호출하던 아키텍처 관점에서 큰 차이가 없다고 한다. (공감...)

MSA를 잘 나눠봐야 자료를 공유하는 곳이 발생하면 말짱 도루묵

특히 통신 인터페이스에서 한쪽에서 변경 사항이 다른 쪽에서 변경을 강요하는 경우가 발생한다.

확장의 경우에도 MSA 뿐만 아니라 엔터프라이즈 패턴(내부에서 큐 쓰는 것)이나 모놀리틱에서도 가능하다. (가능하긴 한 거지 불편한 건 맞는데)

택시 배차 서비스를 고양이 배달 서비스로 바꾼다면 변경해야 할 부분은? 전부다, 즉 아키텍처를 아무리 이쁘게 잘 나눠놔도 근간을 뒤흔드는 요구사항은 언제든지 발생 가능하다.

결론은 MSA를 적용한다고 해도 '상호 결합'이나 '배포 독립성'을 얻는 것은 다른 이야기라는 소리

 

테스트 경계

테스트가 중요한 건 맞는 게 아키텍처 관점에서는 세부사항

독립적으로 배포 가능하며, 의존성 없음 (테스트에 의존하는 건 말이 안 되긴 하지)

근데 아키텍처 중 하나는 맞다고 함

그리고 깨지기 매우 쉽다고

(매우 공감 UI 바꾸면 UI 테스트는 많은 수가 실패할 수도 있다.)

테스트용 API를 따로 만드는 게 맞나...? 이건 공감하기 좀 어렵다. (관리하기가 어렵고 나중에 소스코드를 보는 사람은 이 히스토리를 모를 확률이 크다)

결론에 나온 사항이긴 한데 테스트는 유지 보수하기 어렵기 때문에 버려진다고 한다.

매우 공감... 어떻게 해야지 테스트의 수명을 길게 가져갈 수 있을까

 

클린 임베디드 아키텍처

생략

(펌웨어와 소프트웨어를 잘 분리하고 펌웨어에는 하드웨어 종속적인 부분만 남겨서 소프트웨어를 변경/확장 가능하도록 유지하라는 내용들)

 

데이터베이스는 세부사항이다

데이터가 어디에 어떤 식으로 저장되어 있는지는 아키텍트 관점에서는 몰라도 된다고 한다.

쓰고 나니 오해의 소지가 있긴 한데, RDBMS를 이용하여 디스크에 저장되는지 여부를 애플리케이션에서 몰라야 한다는 소리다.

persistent 저장소는 디스크였으며, 디스크를 잘 활용하기 위해 문서 기반 파일 시스템과, 내용 기반 데이터베이스가 나왔다고 한다.

데이터베이스는 디스크 기반이므로 '느리기' 때문에 인덱스, 캐시, 쿼리 플랜 등등이 필요했다고 한다.

 

이런 것들은 '세부사항'일 뿐이므로 애플리케이션에서는 모르는 게 맞다고 한다.

 

이런 관점에 공감하기도 하고 갸우뚱하기도 하는데...

갸우뚱하는 부분은 새로운 서비스를 기획서 기반으로 테이블 설계부터 시작해서 차근차근 레이어를 쌓아 올린 경험이 생각보다 그리 나쁘지는 않았다는 점이다. (3년 정도만 유지 보수하다가 넘기고 나왔으니 그럴 것 같다. 확장이 더 필요한 시점이 도래했다면 디비 종속성에 치를 떨었을지도)

공감하는 부분은 최근에 했던 (레거시) 프로젝트가 너무 디비(정확히는 큐브리드)에 종속적이라 개선을 생각할 때마다 큐브리드를 어떻게 걷어 낼 수 있을까로 시작해서, '아... 지금은 무리....'로 끝난다는 점이다.

 

그나마 mongodb, redis 까지도 persistent 저장소로 쓰며 강박에서 어느 정도 벗어났지만,

하다 보면 rdbms 기준의 '테이블 단위', '행 단위' 로직이 애플리케이션에서 나타나는 것 같다.

그냥 '언제든 저장소를 따른 것으로 갈아 끼울 수 있게 짜두자' 정도로 접근하고는 있긴 한데 이런 방법이 맞는지는 잘 모르겠다.

 

웹은 세부사항이다

로직을 서버-클라이언트 방식으로 구현하면서 서버 쪽에서 구현하느냐, 클라이언트 쪽에서 구현하느냐 이런 변화는 지속적으로 있어왔다.

연산을 중앙집중으로 하느냐, 분산 처리하느냐 그때그때 시대상에 맞게 바뀌었다고 한다.

그런 의미에서 웹은 세부사항일 뿐이라고 한다. 입출력 장치로서의...

 

프레임워크는 세부사항이다

프레임워크에 강 결합하지 마라 (아키텍트던, 코드던, 개발자 본인이던)

Autowired를 남발하지 말라는 맥락이 여기서 나온다 (책에서 나오지는 않았지만, 생성자 주입이라는 대안이 있다. Autowired 쓰면 개인적으로는 의존성이 복잡해질수록 테스트 코드 짜기 피곤해진다.)

 

나가는 말

나머지 장이 있긴 한데, 이쯤에서 마무리하는 게 좋겠다는 생각이 들어서 마무리

의도적으로 천천히 읽어봤는데 조금 집중도가 떨어진다. (RL이슈도 있었고...)

나름대로 얻어가자면 '언제나 변경될 것을 감수하고 짜라' 정도가 있을 것 같다.

'' 카테고리의 다른 글

말의 품격  (0) 2021.07.31
에픽테토스의 인생을 바라보는 지혜  (0) 2021.07.29
전설로 떠나는 월가의 영웅  (0) 2021.07.21
아비투스  (0) 2021.06.13
사기꾼 증후군  (0) 2021.04.14
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함