‘클린 아키텍처’ 기술 서적에 대해 학습했던 내용을 정리하기 위한 목적의 TIL 포스팅입니다.🙆♂️
- 소프트웨어 아키텍처를 개발하는 기술에는 정책을 신중하게 분리하고, 정책이 변경되는 야상에 따라 정책을 재편성하는 일도 포함된다.
- 동일한 이유로 동일한 시점에 변경되는 정책은 동일한 수준에 위치하며, 동일한 컴포넌트에 속해야 한다.
- 반대로, 서로 다른 이유로 다른 시점에 변겨오디는 정책은 다른 수준에 위치하며 반드시 다른 컴포넌트로 분리해야 한다.
수준
- 수준은 엄밀히 정의하면 ‘입력과 출력까지의 거리’다.
- 시스템의 입력과 출력 모두로부터 멀리 위치할수록 정책의 수준은 높아진다.
- 입력과 출력을 다루는 정책이라면 시스템 최하위 수준에 위치한다.
- 위 이미지는 간단한 암호화 프로그램이다.
- 입력장치에서 문자를 읽어서, 테이블을 참조하여 문자를 번역 후, 번역된 문자를 출력 장치로 기록한다.
- 데이터 흐름은 굽은 실선 화살표로, 소스 코드 의존성은 곧은 점선으로 표시되어야 한다.
- 번역 컴포넌트는 이 시스템에서 최고 수준의 컴포넌트인데, 입력과 출력에서부터 가장 멀리 떨어져있기 때문이다.
- 주목할 점은 데이터 흐름과 소스 코드 의존성이 항상 같은 방향을 가리키지는 않는다는 사실이다.
- 소스 코드 의존성은 그 수준에 따라 결합되어야 하며, 데이터 흐름을 기준으로 결합되어선 안된다.
1
2
3
4
5
function encrypt() {
while(true) {
writeChar(translate(readChar()))
}
}
- 위는 잘못된 아키텍처다.
- 고수준인 encrypt 함수가 저수준인 readChar 와 writeChar 함수에 의존하기 때문이다.
- 위 이미지는 잘못된 사례를 개선한 아키텍처 모습이다.
- 주목할점은 Encrypt 클래스, CharWriter와 CharReader 인터페이스를 둘러싸고 있는 점선으로된 경계다.
- 이 경계를 횡단하는 의존성은 모두 경계 안쪽으로 향한다.
- 이 경계로 묶인 영역이 이 시스템에서 최고 수준의 구성요소다.
- ConsoleReader와 ConsoleWriter 는 입력과 출력에 가깝기 때문에 저수준이다.
- 고수준의 암호화 정책을 저수준의 입력/출력 정책으로부터 분리시킨 방식에 주목하자.
- 이 암호화 정책을 더 넓은 맥락에서 사용할 수 있다.
입력과 출력에 변화가 생기더라도 암호화 정책은 거의 영향을 받지 않기 때문이다.
- 정책을 컴포넌트로 묶는 기준은 정책이 변경되는 방식에 달렸있다는 사실을 상기하자.
- 단일 책임 원칙(SRP)과 공통 폐쇄 원칙(CCP)에 따르면 동일한 이유로 동일한 시점에 변겨오디는 정책은 함께 묶인다.
- 고수준 정책은 저수준 정책에 비해 덜 빈번하게 변경되고, 보다 중요한 이유로 변경되는 경향이 있다.
- 저수준 정책은(입출력에 가까이 위치한 정책) 더 빈번하게 변경되며, 보다 긴급성을 요하며, 덜 중요한 이유로 변경되는 경향이 있다.
- 예를 들어 입출력 장치가 변경될 가능성은 암호화 알고리즘이 변경될 가능성보다 훨씬 높아 보인다.
- 모든 소스 코드 의존성 방향이 고수준 정책을 향할 수 있도록 정책을 분리했다면 변경의 영향도를 줄일 수 있다.
- 시스템의 최저 수준에서 중요하지 않지만 긴급한 변경이 발생하더라도, 보다 높은 위치의 중요한 수준에 미치는 영향은 거의 없게 된다.(경계, 방화벽..)
- 이 논의는 저수준 컴포넌트가 고수준 컴포넌트에 플러그인되어야 한다는 관점으로 바라볼 수도 있다.(p.197 그림 19.3 이미지참고)
결론
- 이 장에서 설명한 정책에 대한 논의는 단일 책임 원칙, 개방 폐쇄 원칙, 공통 폐쇄 원칙, 의존성 역전 원칙, 안정된 의존성 원칙, 안정된 추상화 원칙을 모두 포함한다.
나의 생각🙌
1) 동기화 람다함수
- 이전 동기화 람다 함수 컴포넌트 다이어그램을 보면 app-main, app-{고객사} 모듈이 app-core 모듈에 대한 의존성을 가지고 있다.
- 즉 저수준(app-main 은 입력 부분이며 app-{고객사 모듈} 모두 입력 부분이라 볼 수 있기에) 컴포넌트가 고수준(고수준의 정책이 담긴, 예를 들어 코드/조직/구성원 데이터를 비교 및 CMS 제품 정책을 유지해줄 수 있는) 컴포넌트 app-core 를 의존하고 있는 형태라 볼 수 있다.
- 하지만 출력 부분(cms 로 update api 를 호출하는) 또한 app-core 에 위치하고 있기에 이 장의 내용에 따르면 별도 컴포넌트로 분리하는게 좋아보인다.
2) 레이어드 아키텍처
- 일반적인 백엔드 레이어드 아키텍처에서 web 모듈과 infra 모듈은 domain 모듈을 참조하고 있다.
- 생각해보면 web 모듈은 입력부분, infra 입출력부분으로 볼 수 있기에 web과 infra 모듈은 저수준이며, domain 모듈은 멀리 떨어져있기에 고수준으로 볼 수 있다.
3) 프론트 아키텍처
- 일반 페이지 모듈(사용자 사이트, CMS)은 디자인 시스템 컴포넌트들을 포함하는 위젯 모듈에 대한 의존성을 가지고 있다.
- 여기서 일반 페이지 모듈(사용자 사이트, CMS)은 입출력에 해당하는 저수준 모듈로 볼 수 있다.