‘클린 아키텍처’ 기술 서적에 대해 학습했던 내용을 정리하기 위한 목적의 TIL 포스팅입니다.🙆♂️
16장 - 독립성
유스케이스
- 시스템 아키텍처는 시스템의 의도를 지원해야 한다.
- 가장 중요한 사항은 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다.
- 이들 행위는 일급 요소이며, 시스템의 최상위 수준에서 알아볼 수 있으므로, 개발자가 일일이 찾아 헤매지 않아도 된다.
- 이들 요소는 클래스이거나 함수 또는 모듈로서 아키텍처 내에서 핵심적인 자리를 차지할 뿐만 아니라, 자신의 기능을 분명하게 설명하는 이름을 가질 것이다.
운영
- 아키텍처는 더 실질적인이며 덜 피상적인 역할을 맡는다.
- 만약 시스템에서 수 밀리초 안에 3차원 빅데이터 테이블에 질의해야 한다면, 반드시 이러한 운영 작업을 허용할 수 있는 형태로 아키텍처를 구조화해야 한다.
- 이는 실질적인 형태가 되어야한다는 것을 뜻한다.
- 이러한 형태를 지원한다는 말은 시스템에 따라 다양한 의미를 지닌다.
- 어떤 시스템에선 시스템의 처리 요소를 일련의 작은 서비스들로 배열하여, 서로 다른 많은 서버에서 병렬로 실행할 수 있게 만들어야 함을 의미하고, 또 다른 시스템에선 경량의 수많은 스레드가 단일 프로세서에서 같은 주소 공간을 공유하도록 만든다는 뜻일 수 있다.
- 이러한 결정은 뛰어난 아키텍트라면 열어두어야 하는 선택사항 중 하나다.
- 모놀리틱 구조에서 MSA로 개선하는 것은 어렵다.
- 하지만 아키텍처에서 각 컴포넌트를 적절히 격리하여 유지하고 컴포넌트간 통신 방식을 특정 형태로 제한하지 않는다면 위 예시처럼 MSA 형태로 전환하는 일이 훨씬 쉬워질 것이다.
개발
- 아키텍처는 개발환경을 지원하는데 있어 핵심적인 역할을 수행한다.
- 콘웨이의 법칙이 작용하는 지점이 바로 여기다.
시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어낼 것이다.
- 각 팀이 독립적으로 행동하기 편한 아키텍처를 반드시 확보하여 개발하는 동안 팀들이 서로 방해되지 않도록 해야 한다.
- 이러한 아키텍처를 만들려면 잘 격리되어 독립적으로 개발 간으한 컴포넌트 단위로 시스템을 분할할 수 있어야 한다.
배포
- 아키텍처는 배포 용이성을 결정하는데 중요한 역할을 한다.
- 목표는
즉각적인 배포
다. - 좋은 아키텍처는 시스템이 빌드된 후 즉각 배포할 수 있도록 지원해야 한다.(CI/CD)
- 위에서도 언급했지만 시스템을 컴포넌트 단위로 적절하게 분할하고 격리시켜야 한다.
- 여기서 마스터 컴포넌트(메인 컴포넌트)도 포함되는데, 마스터 컴포넌트는 시스템 전체를 하나로 묶고, 각 컴포넌트를 올바르게 구동하고 통합하고 관리해야 한다.
선택사항 열어놓기
- 좋은 아키텍처는 컴포넌트 구조와 관련된 이 관심사들 사이에서 균형을 맞추고, 각 관심사 모두를 만족시킨다.
- 하지만 현실에선 이러한 균형 잡기가 매우 어렵다. 도달하려는 목표는 뚜렷하지 않을뿐만 아니라 시시각각 변한다.
- 몇몇 아키텍처 원칙은 구현하는 비용이 비교적 비싸지 않으며, 관심사들 사이에서 균형을 잡는데 도움이 된다.
- 시스템을 제대로 격리된 컴포넌트 단위로 분할할 때 도움이 되며, 이를 통해 선택사항을 가능한 많이, 그리고 가능한 오랫동안 열어 둘 수 있게 해준다.
- 좋은 아키텍처는 선택사항을 열어둠으로써, 향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.
계층 결합 분리
- 하지만 아키텍트는 시스템의 기본적인 의도는 분명히 알고 있다.
- 따라서 단일 책임 원칙과 공통 폐쇄 원칙을 적용하여, 그 의도의 맥락에 따라 다른 이유로 변경되는 것들은 분리하고, 동일한 이유로 변경되는 것들은 묶는다.
- 서로 다른 이유로 변경 되는 것은 무엇일까?
- UI와가 변경되는 이유는 업무 규칙과는 아무런 관련이 없다.
- 그러기에 유스케이스에서 UI부분과 업무 규칙 부분(도메인 규칙)을 서로 분리하고자 할 것이다.
- 이렇게 두 요소를 서로 독립적으로 변경할 수 있을 뿐만 아니라, 유스케이스는 여전히 가시적이며 분명하게 유지할 수 있다.
- 서로 다른 두 유형의 규칙은 각자 다른 속도로, 그리고 다른 이유로 변경될 것이다.
- 현재 개발중인 제품 또한 프론트 백엔드 둘 다 다른 속도 및 다른 이유로 변경되어 가고 있다.
- 따라서 이들 규칙은 서로 분리하고, 독립적으로 변경 가능하도록 만들어야 한다.
- DB, SQL, Schema 조차 기술적 세부사항이고 업무 규칙과는 아무 관련 없다.
- 이들은 시스템의 다른 측면과는 다른 속도로, 그리고 다른 이유로 변경된다.
- 결론적으로 아키텍트는 이들을 시스템의 나머지 부분으로부터 분리하여 독립적으로 변경 가능하도록 해야 한다.
- 이제 우리는 시스템을 서로 결합되지 않은 수평적인 계층으로 분리하는 방법을 알게 되었다.
- 이러한 계층의 예로는 UI, 애플리케이션에 특화된 업무 규칙, 애플리케이션과는 독립적인 업무 규칙, DB 등을 들 수 있다.
유스케이스 결합 분리
- 유스케이스도 서로 다른 이유로 변경되는 것들 중 하나이다.
- 시스템을 수평적 계층으로 분할하면서 동시에 해당 계층을 가로지르는, 얇은 수직적인 유스케이스로 시스템을 분할할 수 있다.
- 이와 같이 결합을 분리하려면 주문 추가 유스케이스의 UI와 주문 삭제 유스케이스의 UI를 분리해야 한다.
- 유스케이스의 업무 규칙과 데이터베이스 부분도 마찬가지다.
- 이런식으로 시스템의 맨 아래 계층까지 수직으로 내려가며 유스케이스들이 각 계층에서 서로 겹치지 않게 한다.
출처: https://jandari91.github.io/posts/PPPCleanArchitecture_ch16/
- 시스템에서 서로 다른 이유로 변경되는 요소들의 결합을 분리하면 기존 요소에 지장을 주지 않고도 새로운 유스케이스를 계속해서 추가할 수 있게 된다.
운영
- 운영 관점에서 도움이 된다.
- UI와 DB 는 업무 규칙과는 다른 서버에서 실행될 수 있기에, 높은 대역폭을 요구하는 유스케이스는 여러 서버로 복제하여 실행할 수 있다.
- 하지만 운영 측면에서 이점을 살리기 위해선 결합을 분리할때 적절한 모드를 선택해야 한다.
- 예를 들어 분리된 컴포넌트를 서로 다른 서버에서 실행해야 하는 상황이라면, 이들 컴포넌트가 단일 프로세서의 동일한 주소 공간에 함께 상주하는 형태로 만들어져선 안된다.
- 분리된 컴포넌트는 반드시 독립된 서비스가 되어야 하고, 네트워크를 통해 서로 통신해야 한다. 많은 아키텍트가 이런 컴포넌트를 ‘서비스’ 또는 ‘마이크로서비스’ 라 부른다.
- 우리는 때때로 컴포넌트를 서비스 수준까지도 분리해야 한다는 것이다.
- 기억해야 할 점은 좋은 아키텍처는 선택권을 열어둔다는 사실이다. 결합 분리 모든느 이러한 선택지중 하나이다.
개발 독립성
- 컴포넌트가 완전히 분리되면 팀 사이의 간섭은 줄어들게 되어 개발 독립성을 보장할 수 있게 된다.
배포 독립성
- 배포 측면에서도 고도의 유연성이 생긴다.
- 운영중인 시스템에서 계층과 유스케이스를 교체할 수 있으며, 새로운 유스케이스를 추가하는 일은 새로운 jar 파일이나 서비스 몇 개를 추가하는 정도로 단순한 일이 된다.
중복
- 소프트웨어에서 중복은 나쁜 것인데 중복은 여러 종류가 있다.
- 하나는 진짜 불필요한 제거해야할 중복이다.
- 다른 하나는 거짓된 또는 우발적 중복이다. 중복으로 보이는 두 코드 영역이 각자의 경로로 발전한다면, 즉 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는 진짜 중복이 아니다. 몇 년이 지나 다시 보면 두코드가 매우 다르다는 사실을 알게 될 것이다.
- 예를 들어 두 유스케이스의 화면 구조가 매우 비슷해서 코드를 통합하게 될 경우 우발적 중복일 가능성이 높다.
- 시간이 지나면서 두 화면은 서로 다른 방향으로 분기하며, 결국엔 매우 다른 모습을 가질 가능성이 높다.
- 이러한 이유로, 해당 코드를 통합하지 않도록 유의해야 한다.
- 그렇지 않으면 나중에 코드를 다시 분리하느라 큰 수고를 감수해야 한다.
나의 생각: 프론트 화면 구현시에 디자인 시스템 위젯 외의 항목들은 모두 따로 구현해야 하는 것일까? 일부 비슷한 컴포넌트는 가젯으로 만들어서 재사용하긴 하지만, 만약 기획&디자인과 협의한 UI들에 대해서도 무조건 별도 분리해야 하는 것일까?
- DB 레코드와 UI 화면이 비슷해도 그대로 전달하기보단 뷰모델을 통해 계층 간 결합을 적절하게 분리하여 유지해라.
나의 생각: MVVM 아키텍처도 이러한 계층 간의 책임을 나누고 계층 간의 결합을 분리하고자 하는 것이 핵심이 아닐까 싶다. Model 에선 데이터를 관리하는 책임만, ViewModel 에선 View 에 전달하기 위한 데이터 가공 및 연결의 책임만, View 에서 UI의 책임만 가질 수 있도록 말이다.
결합 분리 모드(다시)
- 계층과 유스케이스의 결합을 분리하는 방법은 다양한다.
1) 소스 수준 분리 모드
- 소스 코드 모듈 사이의 의존성을 제어할 수 있다.
- 이를 통해 하나의 모듈이 변하더라도 다른 모듈을 변경하거나 재컴파일하지 않도록 만들 수 있다.(예, 루비 Gem)
- 모든 컴포넌트가 같은 주소 공간에서 실행되고, 서로 통신할 때는 간단한 함수 호출을 사용한다. 컴퓨터 메모리에는 하나의 실행 파일만이 로드된다.
- 이러한 구조를 모노리틱 구조라 부른다.
- 소스 코드 모듈 사이 접근제어자를 통한 분리로 생각하며 될 것 같다.
2) 배포 수준 분리 모드
- jar 파일, DLL, 공유 라이브러리와 같이 독립적으로 배포 가능한 단위들 사이의 의존성을 제어할 수 있다.
- 이를 통해 한 모듈의 소스 코드가 변하더라도 다른 모듈을 재빌드하거나 재배포하지 않도록 만들 수 있다.
- 많은 컴포넌트가 여전히 같은 주소 공간에 상주하며, 단순한 함수 호출을 통해 통신할 수 있다.
- 멀티 모듈 구조에서 하나의 모듈을 예로 들 수 있을 것이다.
3) 서비스 수준 분리 모드
의존하는 수준을 데이터 구조 단위까지 낮출 수 있고, 순전히 네트워크 패킷을 통해서만 통신하도록 만들 수 있다.(예, 서비스 또는 마이크로서비스)
- 어떤 모드가 사용하기 좋을까? 프로젝트 초기 다계는 어떤 모드가 최선인지 알기 어려우며 프로젝트가 성숙해갈수록 최적인 모드가 달라질 수 있다.
- 시스템이 한 서버에서 실행되는 동안은 결합은 소스 수준에서 분리하는 것만으로도 충분하다.
- 하지만 나중에는 배포 가능한 단위, 심지어는 서비스 수준까지 분리해야 할 수도 있다.
- (현시점에 가장 인기있어 보이는) 한 가지 해결책은 단순히 서비스 수준에서의 분리를 기본 정책으로 삼는 것이다.
- 하지만 이 방식은 비용이 많이 들고 결합이 큰 단위에서 분리된다는 문제가 있다.(새로운 레포 생성하고 프로비저닝하고 CI/CD 작업하고 애플리케이션 기반작업해야 하고.., 모든 마이크로서비스의 공통 작업이 있을때마다 전부 챙겨줘야 하고..)
- 마이크로서비스 가 아무리 작다(micro)하더라도, 충분히 작은 단위에서 분리될 가능성은 거의 없다.
- 서비스 수준의 결합 분리가 지닌 또 다른 문제점은 개발 시간 측면뿐 아니라 시스템 자원 측면에서도 비용이 많이 든다는 사실이다.
- 저자는 이처럼 컴포넌트가 서비스화될 가능성이 있따면 컴포넌트 결합을 분리하되 서비스가 되기 직전에 멈추는 방식을 선호한다고 한다. 그러고는 컴포넌트들을 가능한 오랫동안 동일한 주소 공간에 남겨두어 서비스에 대한 선택권을 열어 둔다고 한다.
- 위 방식을 사용하면 초기엔 컴포넌트가 소스 코드 수준에서 분리된다.
- 프로젝트 수행 기간엔 넉넉할 것이다.
- 배포나 개발에서 문제가 생기면 일부 결합을 배포 수준까지 분리해 대응하면 된다.
- 개발, 배포, 운영적인 문제가 증가하면 서비스 수준으로 전환할 배포 단위들을 신중하게 선택 후, 점차적으로 서비스화하는 방향으로 시스템을 변경해나간다.
- 시간이 흐르면 시스템에서 운영 요구사항은 감소할 수 있는데, 이때 결합을 서비스 수준까지 분리해야 했던 것들이 이제 배포 수준, 심지어 소스 수준의 결합 분리만으로 충분할 수도 있다.
- 좋은 아키텍처는 시스템이 모노리틱 구조로 태어나서 단일 파일로 배포되더라도, 이후엔 독립적으로 배포 가능한 단위들의 집합으로 성장하고, 또 독립적인 서비스나 마이크로서비스 수준까지 성장할 수 있도록 만들어져야 한다. 또한 나중에 상황이 바뀌었을때 이 진행 방향을 거꾸로 돌려 원래 형태인 모노리틱 구조로 되돌릴 수도 있어야 한다.
- 좋은 아키텍처는 이러한 변경으로부터 소스 코드 대부분을 보호한다.
- 좋은 아키텍처는 결합 분리보드를 선택사항으로 남겨두어 배포 규모에 따라 가장 적합한 모드를 선택해 사용할 수 있게 만들어 준다.
결론
- 위처럼 하기는 까다롭다. 그리고 결합 분리 모드를 변경하기가 설정 값 하나 바꾸듯 쉬워야 한다는 뜻도 아니다.
- 시스템의 결합 분리 모드는 시간이 지나면서 바뀌기 쉬우며, 뛰어난 아키텍트라면 이러한 변경을 예측하여 큰 무리 없이 반영할 수 있도록 해야 한다.
- 소스 수준 분리 -> 배포 수준 분리 -> 서비스 수준 분리
- 소스 수준 분리 <- 배포 수준 분리 <- 서비스 수준 분리