소개
클린아키텍처: 소프트웨어 구조와 설계의 원칙 책을 읽고 정리하며 소감을 적는 포스트입니다.
아키텍처란?
아키텍처(architecture)라는 단어는 권력과 신비로움을 연상케 한다. 소프트웨어아키텍처는 기술적 성취의 정점에 서 있다. 소프트웨어 아키텍트를 생각할 때면, 권한을 가지며 존경심을 불러일으키는 사람을 떠올린다.
그러면 소프트웨어 아키텍처란 무엇인가? 소프트웨어 아키텍트는 무슨 일을 하며, 언제 그 일을 하는가?
무엇보다도 소프트웨어 아키텍트는 프로그래머이며, 앞으로도 계속 프로그래머로 남는다. 소프트웨어 아키텍트라면 코드에서 탈피하고 고수준의 문제에 집중해야 한다는 거짓말에 절대로 속아서는 안된다.
소프트웨어 아키텍트는 최고의 프로그래머이며, 앞으로도 계속 프로그래밍 작업을 맡을 뿐만 아니라 동시에 나머지 팀원들이 생산성을 극대화할 수 있는 설계를 하도록 방향을 이끌어 준다.
소프트웨어 시스템의 아키텍처란 시스템을 구축했던 사람들이 만들어낸 시스템의 형태이다. 그 모양은 시스템을 컴포넌트로 분할하는 방법, 분할된 컴포넌트를 배치하는 방법, 컴포넌트가 서로 의사소통하는 방식에 따라 정해진다.
그리고 그 형태는 아키텍처 안에 담긴 소프트웨어 시스템이 쉽게 개발, 배포, 운영, 유지보수 되도록 만들어진다.
이러한 일을 용이하게 만들기 위해서는 가능한 한 많은 선택지를, 가능한 한 오래 남겨두는 전략을 따라야 한다.
아키텍처의 주된 목적은 시스템의 생명주기를 지원하는 것이다. 좋은 아키텍처는 시스템을 쉽게 이해하고, 쉽게 개발하며, 쉽게 유지보수하고, 또 쉽게 배포하게 해준다.
아키텍처의 궁극적인 목표는 시스템의 수명과 관련된 비용은 최소화하고, 프로그래머의 생산성은 최대화하는 데 있다.
개발
개발하기 힘든 시스템이라면 수명이 길지도 않고 건강하지도 않을 것이다.
시스템 아키텍처는 개발팀(들)이 시스템을 쉽게 개발할 수 있도록 뒷바침해야한다.
팀 구조가 다르다면 아키텍처 관련 결정에도 차이가 난다. 개발자가 다섯명이라면 서로 효율적으로 협력하며 모노리틱(monilithic)시스템을 개발 할 수 있다.
다른 한편으로 일곱 명씩 구성된 총 다섯팀이 시스템을 개발하고 있다면 시스템을 신뢰할 수 있고 안정된 인터페이스를 갖춘, 잘 설계된 컴포넌트 단위로 분리하지 않으면 개발이 진척되지 않는다. 다른 요소를 고려하지 않는다면 이 시스템의 아키텍처는 다섯 개의 컴포넌트로(즉, 각 팀마다 하나씩) 발전될 가능성이 높다.
이러한 팀별 단일 컴포넌트
아키텍처가 시스템을 배포, 운영, 유지보수 하는데 최적일 가능성은 거의 없다. 그럼에도 여러 팀이 순전히 일정에만 쫒겨서 일한다면, 결국 이 아키텍처로 귀착될 것이다.
배포
배포 비용이 높을 수록 시스템의 유용성은 떨어진다. 따라서 소프트웨어 아키텍처는 시스템을 단 한 번에 쉽게 배포할 수 있도록 만드는 데 그 목표를 두어야 한다.
안타깝지만 초기 개발 단계에서는 배포 전략을 거의 고려하지 않는다.
예를 들어 개발 초기에 마이크로서비스 아키텍처
를 사용하자고 결정한다면 컴포넌트 경계가 뚜렿해지고 인터페이스가 대체로 안정화되므로 시스템을 매우 쉽게 개발 할 수 있다고 판단했을지도 모른다.
하지만 배포할 시기가 되면 위협적일 만큼 늘어난 수많은 마이크로서비스를 발견하게 될지도 모른다.
만약 아키텍트가 배포 문제를 초기에 고려했다면 이와는 다른 결정을 내렸을 것이다.
더 적은 서비스를 사용하고, 서비스 컴포넌트와 프로세스 수준의 컴포넌트를 하이브리드 형태로 융합하며, 좀 더 통합 된 도구를 사용하여 상호 연결을 관리했을 것이다.
운영
아키텍처가 시스템 운영에 미치는 영향은 개발, 배포, 유지보수에 미치는 영향보다는 덜 극적이다. 운영에서 겪는 대다수의 어려움은 소프트웨어 아키텍처에는 극적인 영향을 주지 않고도 단순히 하드웨어를 더 투입해서 해결할 수 있다.
하드웨어는 값 싸고 인력은 비싸다는 말이 뜻하는 바는 운영을 방해하는 아키텍처가 개발, 배포, 유지보수를 방해하는 아키텍처보다는 비용이 덜 든다는 뜻이다.
시스템을 쉽게 운영하게 해주는 아키텍처가 바람직하지 않다는 말이 아니다. 다만 비용 공식 관점에서 운영보다는 개발, 배포, 유지보수 쪽으로 더 기운다는 말이다.
그렇다라도 시스템을 운영할 때 아키텍처가 맡은 또 다른 역할이 있다. 좋은 소프트웨어 아키텍처는 시스템을 운영하는 데 필요한 요구도 알려준다.
아키텍처의 이 역할을 달리 표현하면, 시스템 아키텍처가 개발자에게 시스템의 운영 방식을 잘 드러내 준다고 할 수 있다.
시스템 아키텍처는 유스케이스, 기능, 시스템의 필수 행위를 일급(first-class) 엔티티로 격상시키고, 이들 요소가 개발자에게 주요 목표로 인식되도록해야 한다.
유지보수
유지보수는 모든 측면에서 봤을 때 소프트웨어 시스템에서 비용이 가장 많이 든다.
유지보수의 가장 큰 비용은 탐사(spelunking)와 이로 인한 위험부담에 있다.
탐사란 기존 소프트웨어에 새로운 기능을 추가하거나 결함을 수정할 때, 소프트웨어를 파헤쳐서 어디를 고치는 게 최선인지, 그리고 어떤 전략을 쓰는게 최적일지 결정할 때 드는 비용이다.
주의를 기울여 신중하게 아키텍처를 만들면 이 비용을 크게 줄일 수 있다. 시스템을 컴포넌트로 분리하고, 안정된 인터페이스를 두어 서로 격리한다.
이를 통해 미래에 추가 될 기능에 대한 길을 밝혀 둘 수 있을 뿐만 아니라 의도치 않은 장애가 발생할 위험을 크게 줄일 수 있다.
선택사항 열어두기
소프트웨어는 두 종류의 가치, 즉 행위적 가치와 구조적 가치를 지닌다. 이 중에서 구조적 가치가 더 중요한데, 소프트웨어를 부드럽게(soft) 만들기 때문이다.
소프트웨어를 부드럽게 유지하는 방법은 선택사항을 가능한 한 많이, 그리고 가능한 한 오랫동안 열어 두는 것이다. 그렇다면 열어둬야 할 선택사항은 무엇인가?? 바로 중요치 않은 세부사항(detail)이다.
모든 소프트웨어 시스템은 주요한 두 가지 구성요소로 분해 할 수 있다.
바로 정책(policy)과 세부사항이다. 정책 요소는 모든 업무 규칙과 업무 절차를 구체화 한다. 정책이란 시스템의 진정한 가치가 살아 있는 곳이다.
세부사항은 사람, 외부 시스템, 프로그래머가 정책과 소통할 때 필요한 요소지만, 정책이 가진 행위에는 조금도 영향을 미치지 않는다.
예를 들어 세부사항에는 입출력 장치, 데이터베이스, 웹 시스템, 서버, 프레임워크, 통신 프로토콜 등이 있다.
아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고, 동시에 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는데 있다. 이를 통해 세부사항을 결정하는 일을 미루거나 연기할 수 있게 된다.
좋은 아키텍트는 결정되지 않은 사항의 수를 최대화 한다.
장치 동립성
이전 프로그래머의 대표적인 실수 중 하나는 코드를 입출력 장치와 직접 결합해버린 일이었다. 프린터로 인쇄할 일이 있다면, 해당 프린터를 제어하는 입출력 명령어를 직접 사용해서 코드로 작성했다. 이러한 코드는 장치 종속적(device dependent)이 었다.
1960년 후반에 어르러서야 장치 독립성(device independence)를 생각해 냈다. 오늘날의 운영체제는 입출력 장치를 소프트웨어 함수로 추상화했고, 해당 함수는 천공카드와 같은 단위 레코드를 처리한다.
동일한 프로그램을 아무런 변경 없이도 카드에서 읽고 쓰거나 테이브에서 읽고 쓸 수 있게 되었다.
개발 폐쇄 원칙이 탄생한 순간이다.
광고 우편
1960년 대 후반에 나는 광고 우편을 인쇄하는 회사에서 일했다. 의뢰인은 고객의 이름과 주소가 포함한느 단위 레코드가 기록 된 자기 테이프를 우리에게 보내주고 우리는 개인화된 광고를 멋지게 프린트하는 프로그램을 작성하였다.
그러나 안타깝게도 IBM 360과 라인 프린트 하나만 이용해서 인쇄했고 매우 느렸다. 그래서 우리는 자기 테이프를 사용하도록 운영체제에게 지시하여 IBM 360가 10여분 만에 자기 테이프를 가득 채워서 쏟아냈다. 우리는 그 것을 가지고 오프라인 프린트로 수십만 장의 광고 우편을 인쇄하였다.
장치 동립성이 지닌 가치는 굉징했다. 어떤 장치를 사용할지 전혀 모른채, 그리고 고려하지 않고도 프로그램을 작성할 수 있었다.
이 경우 정책은 이름과 주소 레코드에 대한 서식이었다. 세부사항은 장치였다. 우리는 어떤 장치를 사용할지에 대한 결정을 연기시켰다.
물리적 주소할당
1970년대 초에 나는 회계 시스템을 만들고 있었다. 25MB의 크기의 디스크 드라이브에 여러 Agent, Employer, Member의 레코드를 저장했다.
그렇게 우리는 소프트웨어가 디스크의 상세 구조를 알도록 만들 었다. 즉 소프트웨어는 디스크가 200개의 실린더와 10개의 헤드로 구성되며, 각 실린더는 헤드별 수십 개의 섹터로 구성된다는 사실을 알게 되었다. 이러한 정보가 모드 하드 코딩이었다.
그런데 만약 헤더가 더 많거나 실린더가 더 많은, 또는 실린더당 섹터가 더 많은 새로운 디스크 드라이브로 업그레이드해야 한다면 무슨 일이 벌어질까? 하드 코딩된 코드를 전부 수정해야 한다.
어느날 노련한 프로그래머가 우리 조직에 합류하였고 그는 친절하게 주소 할당 체계를 변경하여 상대 주소를 사용하라고 충고해 주었다.
다행이도 우리는 그의 조언을 받아들여, 시스템에서 고수준의 정책이 디스크의 물리적 구조로부터 독립되도록 수정했다. 그 덕분에 우리는 디스크 드라이브 구조에 대한 결정사항을 애플리케이션으로부터 분리할 수 있게 되었다.
결론
좋은 아키텍트는 세부사항을 정책으로 부터 신중하게 가려내고, 정책이 세부사항과 결합되지 않도록 엄격하게 분리한다.
이를 통해 정책은 세부사항에 관한 어떤 지식도 갖지 못하게 되며, 어떤 경우에도 세부사항에 의존하지 않게 된다.
좋은 아키텍트는 세부사항에 대한 결정을 가능한 한 오랫동안 미룰 수 있는 방향으로 정책을 설계한다.