소개
클린아키텍처: 소프트웨어 구조와 설계의 원칙 책을 읽고 정리하며 소감을 적는 포스트입니다.
경계: 선 긋기
소프트웨어 아키텍처는 선을 긎는 기술이며, 나는 이러한 선을 경계(boundary)라고 부른다.
경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는다.
프로젝트 초기에 그어진 선은 가능한 오랫동아 결정을 연기시키기 위해, 그래서 이들 결정이 핵심적인 업무 로직을 오염시키지 못하도록 만들려는 목적으로 쓰인다.
아키텍트의 목표는 인적 자원을 최소화하는 것이다. 인적 자원의 효율을 떨어뜨리는 요인은 바로 결합(coupling)이다. 특히 너무 일찍 내려진 결정에 따른 결합이다.
이러한 결정은 시스템의 업무 요구사항, 즉 유스케이스와 아무런 관련이 없는 결정이다. 프레임워크, 데이터베이스, 웹 서버, 유틸리티 라이브러리, 의존성 주입에 대한 결정들이 포함된다.
좋은 시스템 아키텍처는 이러한 결정을 가능한 한 최후의 순간에 내릴 수 있게 해주며, 결정에 따른 영향이 크지 않게 만든다.
두 가지 슬픈 이야기
P사의 슬플 이야기
먼저 P 회사의 슬픈 이야기는 1990년대 데스크톱 GUI 애플리케이션으로 성장했다. 하지만 웹이 대세가 되면서 자바 프로그래머를 다수 고용 했고, 자사 제품을 웹 버전으로 변환하는 프로젝트에 착수 했다.
자바 진영 사람은 머릿속에 서버 팜(server farm)이 춤추는 이상을 꿈꾸었기에, 3-티어로 구성된 리치(rish) 아키텍처를 채택했고, 서버 팜을 통해 분산하고자 했다.
서버 팜에는 GUI를 위한 서버, 미들웨어 서버, 데이터베이스 서버가 있었다. 이들 프로그래머는 모든 도메인 객체가 세 가지 인스턴스를 가져야 한다고 너무 이른 결정을 내렸다.
하나는 GUI 티어를 위해, 또 하나는 미들웨어 티어를 위해, 나머지 하나는 데이터베이스 티어를 위해서이다.
이들 인스턴스는 서로 다른 머신에 상주했기 때문에 티어 간 메서드 호출은 객체로 변환하여, 직렬화 한 후, 회선을 통해 마샬링(marshalling) 되었다.
역설적이게도 P사는 서버 팜을 필요로 하는 시스템을 한번도 판매하지 못했다. 배포했던 시스템은 모두 단일 서버였다. 그리고 단일 서버에서 세 실행 파일은 객체 초기화, 직렬화, 마샬링과 언마샬링, 메시지 구성과 파싱, 소켓 통신들과 추가 작업들을 지속했다.
P사의 실수는 아키텍트가 너무 이르게 결정을 내림으로써 개발 비용을 엄청나게 가중시킨 사례다.
W사의 슬픈 이야기
일련의 회사 차량을 관리하는 지역 기업인 W사를 살펴보자. 이들은 최근 아키텍트
를 고용하였고, 그는 모든 특성이 구성된 엔터프라이즈급의 서비스 지향 아키텍처
가 필요하다는 것을 파악했다.
업무와 관련된 서로 다른 모든 객체
들로 구성된 거대한 도메인 모델을 생성했고, 이들 도메인 객체를 관리하기 위해 서비스들의 묶음을 설계했으며, 모든 개발자를 지옥의 길로 밀어 넣었다.
당연하겠지만 무언가를 테스트하려면 필요한 서비스들을 하나씩 구동시키고 메시지 버스와 BPel(Business Process Execution Language)서버 등을 작동시켜야 했다.
이들 모든 서비스 사이의 결합으로 인해 엄청난 양의 WSDL(Web Services Description Language)를 변경해야하며, 변경에 영향받는 모든 것을 다시 배포해야 할 것이다.
W사의 실수는 SOA를 약속하는 일련의 도구들을 너무 일찍 채택하여 적용했다는 사실이다.
FitNesse
나는 2001년에 FitNesse를 만들기로 하였다. 이때는 메이븐이 등장하여 jar 파일 문제를 해결하기 전이였다.
우리가 초기에 내린 결정 중 하나는 FitNesse의 요구에 특화된 우리만의 웹서버를 직접 작성하자는 것이였다. 기본 뼈대만 갖춘 웹 서버는 단일 소프트웨어이기에 구현이 간단하고 어떤 웹 프로그램워크를 사용할지에 대한 결정을 훨씬 나중으로 연기할 수 있었다.
초기에 내린 또 다른 결정은 데이터베이스에 대해 고민하지 말자는 것이였다. 어떤 데이터베이스를 사용하더라도 상관 없도록 설계하여 의도적으로 데이터베이스에 대한 결정을 미뤘다. 우리는 모든 데이터 접근 영역과 데이터 저장소 영역 사이에 인터페이스를 추가하는 간단한 설계 방식을 사용했다.
자그마치 18개월 동안 데이터베이스가 없다는 사실은 스키마와 관련된 문제들, 쿼리 문제들, 데이터베이스 서버 문제들, 패스워드 문제들 그리고 데이터베이스를 작동시킬 때 추하게 고개를 드는 여타 모든 고약한 문제가 없었다는 사실을 뜻한다.
테스트를 느리게 만드는 데이터베이스가 없으니 테스트 또한 빠르게 돌릴 수 있었다.
간단히 말해서 경계선을 긋는 행위는 결정을 늦추고 연기하는 데 도움이 되었고, 궁극적으로는 시간을 엄청나게 절약해주었으며, 골치를 썩지 않게해주었다.
어떻게 선을 그을까? 그리고 언제 그을까?
관련이 있는 것과 없는 것 사이에 선을 긋는다. GUI는 업무 규칙과는 관련 없기 때문에, 이 둘 사이에는 반드시 선이 있어야 한다.
데이터베이스는 GUI와는 관련이 없으므로, 이 둘 사이에도 반드시 선이 있어야 한다.
데이터 베이스는 업무 규칙과 관련 없으므로, 이 둘 사이에도 선이 있어야 한다.
데이터베이스는 업무 규칙이 간접적으로 사용할 수 있는 도구다. 업무 규칙은 스키마, 쿼리 언어, 또는 데이터베이스와 관련된 나머지 세부사항에 대해 어떤 것도 알아서는 안된다.
아래의 그림에서 보면 BusinessRules는 Database Interface를 사용하여 데이터를 로드하고 저장한다. DatabaseAccess는 DatabaseInterface를 구현하며, Database를 실제로 자작하는 일을 맡는다.
인터페이스 뒤로 숨은 데이터베이스
경계선은 어디에 있는가? 경계선은 상속 관계를 횡단하면서 Database Interface 바로 아래에 그어진다.
경계선
이제 조금 물러 나서 많은 업무 규칙이 포함된 컴포넌트, 데이터베이스와 데이터베이스 접근 클래스를 포함하는 컴포넌트를 살펴본다
업무 규칙과 데이터베이스 컴포넌트
Database는 BusinessRules에 대해 알고 있다. BusinessRules는 Database에 관해 알지 못한다. 이는 DatabaseInterface 클래스는 BusinessRules 컴포넌트에 속하며, DatabaseAccess 클래스는 Database 컴포넌트에 속한다는 사실을 의미한다.
Database 컴포넌트는 다양한 구현체로 교체될 수 있으며, BusinessRules는 조금도 개의치 않는다.
이 같은 사실은 데이터베이스에 대한 결정은 연기할 수 있으며, 데이터베이스를 결정하기에 앞서 업무 규칙을 먼저 작성하고 테스트하는 데 집중 할 수 있음을 의미한다.
입력과 출력은?
개발자와 고객은 종종 시스템이 무엇인지에 대해 혼란스러워한다. GUI를 보고선 GUI가 시스템이라고 생각하곤한다.
우리는 시스템의 행위를 입출력이 지닌 행위적 측면에서 생각하는 경향이 있다. 예를 들어 비디오게임에서 사용자 경험은 인터페이스에 의해 좌우된다. 화면, 마우스, 버튼, 음향이 바로 그 인터페이스다.
이러한 인터페이스 뒤에는 인터페이스를 조작하는 모델(데이터 구조와 함수로 구성된 정교한 집합)이 존재한다는 사실을 잊어버린다. 더 중요한 사실은 모델은 인터페이스가 필요하지 않다. 화면에 출력되지 않아도 돌아가는데 문제가 없다.
중요한 것은 업무 규칙이다.
GUI와 BusinessRules 컴포넌트 사이의 경계
GUI와 BusinessRules 컴포넌트가 경계선에 의해 분할된다는 사실을 알 수 있다. GUI는 다른 종류의 인터페이스로 얼마든지 교체할 수 있다.
플러그인 아키텍처
데이터베이스와 GUI에 대해 내린 두 가지 결정을 하나로 합쳐서 보면 컴포넌트 추가와 관련한 일종의 패턴이 만들어진다. 이 패턴은 시스템에서 서드 파티 플러그인을 사용할 수 있게 한 바로 그 패턴과 동일하다.
사실 소프트웨어 개발 기술의 역사는 플러그인을 손쉽게 생성하여, 확장 가능하며 유지보수가 쉬운 시스템 아키텍처를 확립할 수 있게 만드는 방법에 대한 이야기다.
선택적이거나 또는 수많은 다양한 형태로 구현될 수 있는 나머지 컴포넌트로부터 핵심적인 업무 규칙은 불리되어 있고, 또한 독립적이다.
업무 규칙에 플러그인 형태로 연결하기
이 설계에서 사용자 인터페이스는 플러그인 형태로 고려되었기에, 수많은 종류의 사용자 인터페이스를 플러그인 형태로 연결할 수 있게 된다.
교체 작업은 사소한 것이 아닐 것이다. 시스템의 초기 배포본이 웹 기반이었다면 클라이언트-서버 UI용 플러그인을 작성하는 것은 쉽지 않은 일이 될 수 있다.
그러다 하더라도 플러그인 구조를 가정할 채 시작함으로써, 최소한 우리는 이러한 변경 작업을 현실성 있도록 만들었다.
플러그인에 대한 논의
ReSharper와 비주얼 스튜디오(Visual Studio)의 관계를 보자. 어느 팀이 다른 팀을 위험하게 만들 수 있을까? 의존성 구조가 답해준다.
ReSharper는 비주얼 스튜디오에 의존한다.
ReSharper는 비주얼 스튜디오의 소스 코드에 의존하기 때문에 비주얼 스튜디오 팀은 원한다면 언제든지 ReSharper팀을 완전히 무력화할 수 있다.
우리는 시스템에서 한 부분이 변경되더라도 관련 없는 나머지 부분이 망가지길 원치 않는다.
경계는 변경의 축(axis of change)이 있는 지점에 그어진다. 경계의 한쪽에 위치한 컴포넌트는 경계 반대편의 컴포넌트와는 다른 속도로, 그리고 다른 이유로 변경된다.
업무 규칙은 의존성 주입 프레임워크와는 다른 시점에 그리고 다른 이유로 변경되므로, 둘 사이에도 반드시 경계가 필요하다.
이 역시도 순전히 단일 책임 원칙에 해당한다. 단일 책임 원칙은 어디에 경계를 그어야 할지를 알려준다.
결론
소프트웨어 아키텍처에서 경계선을 그리려면 먼저 시스템을 컴포넌트 단위로 분할해야 한다. 일부 컴포넌트는 핵심 업무 규칙에 해당한다.
이는 의존성 역전 원칙과 안정된 추상화 원칙을 응용한 것임을 눈치챌 수 있어야 한다. 의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배칳야 한다.