상세 컨텐츠

본문 제목

[Architecture] 5부 아키텍처 : 16장 독립성

프로그래밍/Architecture

by jisooo 2024. 1. 14. 17:44

본문

 

좋은 아키텍처는 다음의 요소들을 지원해야 한다.

  • 시스템의 유스케이스
  • 시스템의 운영
  • 시스템의 개발
  • 시스템의 배포

유스케이스

아키텍처의 최우선 관심사는 유스케이스이며, 아키텍처에서도 유스케이스가 최우선이다.

시스템 아키텍처는 시스템의 의도를 지원해야한다는 뜻이다.

좋은 아키텍처가 행위를 지원하기 위해 할 수 있는 일 중 가장 중요한 사항은

시스템의 행위를 명확히 하고 외부로 드러내며,

이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 하는 것이다.

예를 들어 장바구니 애플리케이션이 좋은 아키텍처를 갖추었다면,

이 애플리케이션은 장바구니 애플리케이션처럼 보일 것이다.

해당 시스템의 유스케이스는 시스템 구조 자체에서 한 눈에 드러날 것이다.

이들 행위는 일급 요소이며,

시스템의 최상위 수준에서 알아볼 수 있으므로,

개발자가 일일이 찾아 헤매지 않아도 된다.

이들 요소는 클래스, 함수, 모듈로서 아키텍처 내에서 핵심적인 자리를 차지할 뿐 아니라,

자신의 기능을 분명히 하는 이름을 갖고 있을 것이다.

운영

시스템의 운영 지원 관점에서 볼 때, 아키텍처는 더 실질적이며 덜 피상적인 역할을 맡는다.

운영 작업을 허용할 수 있는 알맞은 아키텍처를 구조화하는 것에 대한 결정은

뛰어난 아키텍트라면 열어두어야하는 선택사항 중 하나이다.

만약 시스템이 단일체로 작성되어 모놀리틱 구조를 갖는다면,

다중 프로세스, 다중 스레드, 또는 마이크로서비스 형태가 필요할 때 개선하기가 어렵다.

아키텍처에서 각 컴포넌트를 적절히 격리하여 유지하고

컴포넌트 간 통신 방식을 특정 형식으로 제한하지 않는다면,

시간이 지나 운영에 필요한 요구사항이 바뀌더라도

스레드, 프로세스, 서비스로 구성된 기술 스펙트럼 사이를 전환하는 일이 훨씬 쉬워질 것이다.

개발

아키텍처는 개발 환경을 지원하는 데 있어 핵심적인 역할을 수행한다.

시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것이다.

많은 팀으로 구성되며 관심사가 다양한 조직에서 어떤 시스템을 개발한다면,

각 팀이 독립적으로 행동하기 편한 아키텍처를 반드시 확보하여

개발하는 동안 팀들이 서로를 방해하지 않도록 해야 한다.

이러한 아키텍처를 만들려면 잘 격리되어 독립적으로 개발 가능한 컴포넌트 단위로 시스템을 분할해야 한다.

그래야만 이들 컴포넌트를 독립적으로 작업할 수 있는 팀에 할당할 수 있다.

배포

아키텍처는 배포 용이성을 결정하는 데 중요한 역할을 한다.

이 때 목표는 "즉각적인 배포"다.

좋은 아키텍처는 수십 개의 작은 설정 스크립트나 설정 파일을 약간씩 수정하는 방식을 사용하지 않는다.

좋은 아키텍처는 꼭 필요한 디렉터리나 파일을 수작업으로 생성하게 내버려 두지 않는다.

좋은 아키텍처라면 시스템이 빌드된 후 즉각 배포할 수 있도록 지원해야 한다.

이러한 아키텍처를 만들려면 시스템을 컴포넌트 단위로 적절하게 분할하고 격리시켜야 한다.

여기엔 마스터 컴포넌트도 포함되는데,

마스터 컴포넌트는 시스템 전체를 하나로 묶고,

각 컴포넌트를 올바르게 구동하고 통합하고 관리해야한다.

선택사항 열어놓기

좋은 아키텍처는 컴포넌트 구조와 관련된 이 관심사들 사이에서 적절한 균형을 맞추고 각 관심사를 모두 만족시킨다.

하지만 현실세계에서는 이 균형을 잡기가 매우 어렵다.

대부분 우리는 모든 유스케이스를 알 수 없고,

운영하는데 따르는 제약사항, 팀 구조, 배포 요구사항도 알지 못하기 때문이다.

더 심각한 문제는 이러한 사항들을 알고 있더라도,

시스템이 생명주기의 단계를 하나씩 거쳐감에 따라 이 사항들도 반드시 변해간다는 사실이다.

우리가 도달하려는 목표는 뚜렷하지 않을 뿐만 아니라 시시각각 변한다.

이러한 변화 속에서도,

몇몇 아키텍처 원칙은 구현하는 비용이 비싸지 않고,

관심사들 사이에서 균형을 잡는데 도움이 된다.

이들 원칙은 시스템을 제대로 격리된 컴포넌트 단위로 분할할 때 도움이 되며,

이를 통해 선택사항을 가능한 많이 그리고 가능한 오랫동안 열어둘 수 있게 한다.

좋은 아키텍처는 선택사항을 열어둠으로써,

향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.

계층 결합 분리

아키텍트는 모든 유스케이스를 알지는 못하지만

시스템의 기본적인 의도는 분명히 알고 있다.

아키텍트는 단일 책임 원칙공통 폐쇄 원칙을 적용하여,

그 의도의 맥락에 따라서 다른 이유로 변경되는 것들은 분리하고,

동일한 이유로 변경되는 것들은 묶는다.

서로 다른 이유로 변경되는 것들에 대한 예시를 들어보자.

사용자 인터페이스가 변경되는 이유는 업무 규칙과는 아무런 관련이 없다.

이상적인 아키텍처라면

유스케이스에서 UI 부분과 업무 규칙 부분을 서로 분리해야 할 것이다.

두 요소를 서로 독립적으로 변경할 수 있을 뿐만 아니라,

유스케이스는 여전히 가시적이며 분명하게 유지할 수 있다.

업무 규칙은 그 자체가 애플리케이션과 밀접한 관련이 있거나,

혹은 더 범용적일 수 있다.

예를 들어 입력 필드에 대한 유효성 검사는 애플리케이션 자체와 밀접하게 관련된 업무 규칙이다.

반면 계좌의 이자 계산이나 재고품 집계는 업무 도메인에 더 밀접하게 연관된 업무 규칙이다.

이들은 서로 각자 다른 속도로, 그리고 다른 이유로 변경될 것이다.

따라서 이들 규칙은 서로 분리하고, 독립적으로 변경할 수 있도록 만들어야 한다.

또한 데이터베이스, 쿼리언어, 스키마 같은 사항들을 세부사항으로 분리하고,

업무 규칙이나 UI와는 아무 연관이 없으므로

이들을 시스템의 나머지 부분으로부터 분리하여 독립적으로 변경할 수 있도록 해야만 한다.

UI, 애플리케이션에 특화된 업무 규칙, 애플리케이션과는 독립적인 업무 규칙, 데이터베이스 등을

수평적인 계층으로 분리할 수 있다.

유스케이스 결합 분리

유스케이스 그 자체에도 서로 다른 이유로 변경되는 것이 있다.

주문 입력 시스템에서 주문을 추가하는 유스케이스와

주문을 삭제하는 유스케이스는 틀립없이 다른 속도, 그리고 다른 이유로 변경된다.

유스케이스는 시스템을 분할하는 매우 자연스러운 방법이다.

각 유스케이스는 UI의 일부, 애플리케이션 특화 업무 규칙의 일부, 애플리케이션 독립적 업무 규칙의 일부, 데이터베이스 기능의 일부를 사용한다.

따라서 우리는 시스템을 수평적 계층으로 분할하면서 동시에

해당 계층을 가로지르는 얇은 수직적인 유스케이스로 시스템을 분할할 수 있다.

(아래 이미지 참고)

시스템에서 서로 다른 이유로 변경되는 요소들의 결합을 분리하면,

기존 요소에 지장을 주지 않고도 새로운 유스케이스를 계속해서 추가할 수 있게 된다.

또한 유스케이스를 뒷받침하는 UI와 데이터베이스를 서로 묶어서

각 유스케이스가 UI와 데이터베이스의 서로 다른 관점을 사용하게 되면,

새로운 유스케이스를 추가하더라도 기존 유스케이스에 영향을 주는 일은 거의 없을 것이다.

결합 분리 모드

예를 들어,

유스케이스에서 서로 다른 관점이 분ㅇ리되었다면, 높은 처리량을 보장해야 하는 유스케이스와

낮은 처리량으로도 충분한 유스케이스는 이미 분리되어있을 가능성이 높다.

UI와 데이터베이스가 업무 규칙과 분리되어있다면, UI와 데이터베이스는 업무 규칙과는 다른 서버에서 실행될 수 있다.

높은 대역폭을 요구하는 유스케이스는 여러 서버로 복제하여 실행할 수 있다.

즉 유스케이스를 위해 수행하는 결합 분리 작업들은 운영에도 도움이 된다.

운영 측면에서 이점을 살리기 위해 결합을 분리할 때 적절한 모드를 선택해야 한다.

분리된 컴포넌트는 반드시 독립된 서비스가 되어야 하고,

일종의 네트워크를 통해 서로 통신해야 한다.

이렇게 서비스에 기반한 아키텍처를 흔히 서비스 지향 아키텍처라고 부른다.

우리는 이렇게 때때로 컴포넌트를 서비스 수준까지도 분리하는 것을 고려할 수 있다.

좋은 아키텍처는 선택권을 열어두고, 결합 분리 모드는 이러한 선택지 중 하나이다.

개발, 배포 독립성

컴포넌트가 완전히 분리되면 팀 사이의 간섭이 줄어든다.

기능 팀, 컴포넌트 팀, 계층 팀 혹은 또 다른 형태의 팀이라도,

계층과 유스케이스의 결합이 분리되는 한 시스템의 아키텍처는 그 팀의 구조를 뒷받침해줄 것이다.

유스케이스와 계층의 결합이 분리되면 배포 측면에서도 고도의 유연성이 생긴다.

새로운 유스케이스를 추가하는 일은 시스템의 나머지는 그대로 둔 채,

새로운 jar 파일이나 서비스 몇 개를 추가하는 정도로 단순한 일이 될 것이다.

중복

아키텍처는 종종 중복의 함정에 빠지곤 한다.

소프트웨어에서 중복은 일반적으로 나쁜 것이라고 생각하지만, 진짜 중복과 가짜 중복을 가려낼 수 있어야 한다.

중복으로 보이는 두 코드의 영역이 각자의 경로로 발전한다면,

즉 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는 진짜 중복이 아니다.

몇 년이 지나 다시 보면, 두 코드가 매우 다르다는 사실을 알게 될 것이다.

이런 케이스를 "우발적 중복"이라고 할 수 있다.

이런 우발적 중복의 코드를 통합하지 않도록 주의해야 한다.

유스케이스를 수직으로 분리할 때, 이러한 우발적 중복 문제와 종종 마주치게 되고,

이들 유스케이스를 통합하고 싶은 유혹을 받게 된다.

(실제로 나도 우발적 중복의 유혹을 받아 두 가지 유스케이스를 통합해버리는 잘못된 실수를 저지른 적이 있다..ㅎ)

왜냐하면 이들 유스케이스가 서로 비슷한 화면 구조, 비슷한 알고리즘, 비슷한 데이터베이스 쿼리와 스키마를 갖기 때문이다.

이럴 때 중복이 진짜 중복인지 우발적 중복인지 가려낼 수 있는 눈을 길러야 한다.

마찬가지로 계층을 수평 분리하는 경우, 특정 데이터베이스의 레코드가 특정 화면의 데이터 구조와

상당히 비슷한 케이스도 있다.

이 때 데이터베이스 레코드와 동일한 형태의 뷰 모델을 만들어서 각 항목을 복사하는게 아니라,

데이터베이스 레코드를 있는 그대로 UI까지 전달하고 싶은 욕구가 있을 수 있다.

이러한 중복 역시 "우발적 중복"이며,

뷰 모델을 별도로 만드는 일은 그다지 많은 노력이 들지 않을 뿐 더러,

계층 간 결합을 적절하게 분리하여 유지하는 데도 도움이 된다.

결합 분리 모드 2

저자는 계층과 유스케이스의 결합을 분리하는 방법을 3 가지로 정리한다.

1) 소스 수준 분리 모드

소스 코드 모듈 사이의 의존성을 제어할 수 있다.

하나의 모듈이 변하더라도, 다른 모듈을 변경하거나 재컴파일하지 않도록 만들 수 있다.

2) 배포 수준 분리 모드

jar 파일과 같이 배포 가능한 단위들 사이의 의존성을 제어할 수 있다.

한 모듈의 소스 코드가 변하더라도, 다른 모듈을 재빌드하거나 재배포하지 않도록 만들 수 있다.

결합이 분리된 컴포넌트가 jar 파일, Gem 파일, DLL과 같이 독립적으로 배포할 수 있는 단위로 분할되어 있다.

3) 서비스 수준 분리 모드

의존하는 수준을 데이터 구조 단위까지 낮출 수 있고, 순전히 네트워크 패킷을 통해서만 통신하도록 만들 수 있다.

이를 통해 모든 실행 가능한 단위는 소스와 바이너리 변경에 대해 서로 완전히 독립적이게 된다.

(ex: 마이크로 서비스)

위 처럼, 계층과 유스케이스의 결합을 분리하는 방법은 여러가지가 있지만,

프로젝트 초기 단계에는 어떤 모드가 최선인지 알기가 어렵다.

프로젝트가 성숙해질 수록 최적인 모드도 역시 달라질 수 있다.

당장에는 시스템을 단일 서버로도 안정되게 실행할 수 있더라도,

시스템이 성장하면서 결국에는 배포 가능한 단위, 서비스 수준의 분리를 통해 별도의 서버에서 실행해야만 하는 컴포넌트가 생길 거라고

충분히 예상할 수 있다.

결합 분리 모드의 선택이 어려울 때,

한 가지 해결책은 단순히 서비스 수준에서의 분리를 기본 정책으로 삼는 것이다.

이 방식은 단점이 비용이 많이 들고 결합이 큰 단위에서 분리된다는 문제가 있다.

마이크로서비스가 아무리 작다하더라도, 충분히 작은 단위에서 분리될 가능성이 거의 없다.

서비스 수준의 결합 분리의 또다른 단점은 개발 시간도 오래걸릴 뿐 더러,

시스템 자원 측면에서도 비용이 많이 든다.

필요치 않은 서비스 경계를 처리하는 데 드는 작업은

노력, 메모리, 계산량 측면에서 모두 낭비일 수 있다.

이처럼 컴포넌트가 서비스화될 가능성이 있다면 컴포넌트 결합을 분리하되

서비스가 되기 직전에 멈추는 방식을 선호한다.

그리고 컴포넌트들을 가능한 한 오랫동안 동일한 주소 공간에 남겨둠으로써,

서비스에 대한 선택권을 열어둘 수 있다.

이 방식을 사용하면 초기에 컴포넌트가 소스 코드 수준에서 분리된다.

후에 프로젝트가 성장하면서

개발, 배포, 운영적인 문제가 증가하면 서비스 수준으로 전환할 배포 단위들을 신중히 선택한 후,

점차적으로 서비스로 분리시키는 시스템을 변경해나간다.

결국 좋은 아키텍처는

시스템이 모노리틱 구조로 태어나서 단일 파일로 배포되더라도,

이후에는 독립적으로 배포 가능한 단위들의 집합으로 성장하고,

또 독립적인 서비스나 마이크로서비스 수준까지 성장할 수 있도록 만들어져야 한다.

또한 나중에 상황이 바뀌었을 때 이 진행 방향을 거꾸로 돌려

원래 형태인 모노리틱 구조로 되돌릴 수도 있어야 한다.

좋은 아키텍처는 결국 이러한 변경으로부터 소스 코드 대부분을 보호하고,

결합 분리 모드를 선택사항으로 남겨두어서

배포 규모에 따라 적합한 모드를 사용할 수 있게 만들어준다.

#클린아키텍처 #cleanArchitecture #아키텍처 #독립성

관련글 더보기

댓글 영역