넘치게 채우기

5-3: 경계: 선 긋기 본문

개발/Clean Architecture

5-3: 경계: 선 긋기

riveroverflow 2023. 11. 22. 17:20
728x90
반응형

소프트웨어 아키텍처는 선을 긋는 기술이다.

이러한 선을 경계라고 한다.

경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는다.

이들 선 중 일부는 프로젝트 수명 중 아주 초기에, 심지어 코드가 작성되기도 전에 그어지며, 어떤 선은 매우 나중에 그어진다.

초기에 그어지는 선들은 가능한 한 오랫동안 결정을 연기시키기 위해, 그래서 이들 결정이 핵심적인 업무 로직을 오염시키지 못하게 만들려는 목적으로 쓰인다.

아키텍트의 목표는 필요한 시스템을 만들고 유지하는 데 드는 인적 자원을 최소화하는 것이라는 것을 상기하자. 그렇다면 인적 자원의 효율을 떨어뜨리는 요인은 무엇일까?

바로 결합이다.

특히 너무 일찍 내려진 결정에 따른 결합이다.

어떤 종류의 결정이 이른 결정일까? 바로 시스템의 업무 요구사항, 즉 유스케이스와 아무런 관련이 없는 결정이다.

프레임워크, 데이터베이스, 웹 서버, 유틸리티 라이브러리, 의존성 주입에 대한 결정 등이 여기 포함된다. 좋은 시스템 아키텍처란 이러한 결정이 부수적이며, 결정을 연기할 수 있는 아키텍처다. 그리고 이러한 결정을 가능한 한 최후의 순간에 내릴 수 있게 해주며, 결정에 따른 영향이 크지 않게 만든다.

두 가지 슬픈 이야기

1. P회사의 사례

1980년대 P사의 창립자들은 간단한 모노리틱 데스크톱 애플리케이션을 만들었다.

이들은 큰 성공을 거두었고, 1990년대에 거쳐서 성공적인 데스크톱 애플리케이션으로 성장시켰다.

그러나 1990년대 후반이 되자 웹이 대세가 되기 시작했다.

갑자기 모두가 웹 솔루션을 확보해야만 했고, P사도 예외는 아니었다.

고객들은 웹 기반 버전을 강하게 요구했다.

P사는 잘나가는 20대 자바 프로그래머들을 다수 고용했고, 자사 제품을 웹 버전으로 변환하는 프로젝트에 착수했다.

자바 진영 사람들은 머릿속에서 서버 팜이 춤추는 이상을 꿈꾸었기에, 3-티어로 구성된 리치 “아키텍처”를 채택했고, 서버 팜을 통해 분산하고자 했다. 서버 팜에는 GUI를 위한 서버, 미들웨어 서버, 데이터베이스 서버가 있었을 것이다.

이들 프로그래머는 모든 도메인 객체가 세 가지 인스턴스를 가져야 한다고 너무 이른 결정을 내렸다.

하나는 GUI티어를 위해, 또 하나는 미들웨어 티어를 위해, 나머지 하나는 데이터베이스 티어를 위해서다.

이들 인스턴스는 서로 다른 머신에 상주했기 때문에, 프로세서 간 그리고 티어 간 통신이 필요했고, 결국 리치 시스템이 구성되었다. 티어 간 메서드 호출은 객체로 변환하여 직렬화 한 후, 회선을 통해 마샬링 되었다.

이제 기존 레코드에 새로운 필드를 추가하는 것과 같이 간단한 기능을 구현할 때 무슨 일을 해야 하는지 상상해 보자. 해당 필드를 세 티어에 있는 클래스 모두와 티어 간 메시지 다수에 추가해야 한다.

데이터는 양방향으로 이동하므로 메시지 프로토콜은 반드시 네 개를 설치해야 한다.

각 프로토콜은 송신부와 수신부가 있으므로 핸들러는 여덟 개가 필요하다.

세 개의 실행 파일이 빌드되어야 하므로, 세 개의 업무 객체, 네 개의 새로운 메시지, 그리고 여덟 개의 새로운 핸들러를 포함한다.

이번에는 가장 간단한 기능을 수행할 때 이 실행 파일들이 해야만 하는 일을 생각해 보자. 모든 객체의 초기화, 이 모든 직렬화, 이 모든 마샬링과 언마샬링, 이 모든 메시지에 대한 구성 및 파싱, 이 모든 소켓 통신들, 타임아웃 관리, 재시도 시나리오, 그리고 이 간단한 일 하나를 마무리하기 위해 해야하는 또 다른 모든 추가적인 일들을 생각해 보라.

물론 개발하는 동안 그 프로그래머들은 서버 팜을 확보할 수 없었다.

그러나 자신들의 아키텍처가 옳다고 확신하고, 단일 장비에서 실행함에도 이 모든 해야만 하는 일들을 단일 장비에서 실행하도록 지속했다.

그러나, 역설적이게도 P사는 서버 팜을 필요로 하는 시스템을 한 번도 판매하지 못했고, 그 프로그래머들은 존재한 적도 없고 존재하지 않을 서버팜을 기대하면서 추가 작업들을 지속했다.

2. 회사 차량 관리 지역 기업 W사

최근 이들은 아키텍트를 고용해서 오합지졸 같은 소프트웨어 작업을 통제하에 두고자 했다.

한 가지 덧붙이자면 ‘통제’는 이 친구의 중간 이름이었다. 그는 이 작은 기업에는 모든 특성이 구비된, 엔터프라이즈급의 서비스 지향 ‘아키텍처’가 필요하다는 사실을 재빠르게 파악했다.

그는 업무와 관련된 서로 다른 모든 ‘객체’들로 구성된 거대한 도메인 모델을 생성했고, 이들 도메인 객체를 관리하기 위한 서비스들의 묶음을 설계했으며, 모든 개발자를 지옥의 길로 묶었다.

무언가를 테스트하려면 필요한 서비스들을 하나씩 구동시키고, 메시지 버스와 BPel 서버 등을 작동시켜야 한다.

그리고 메시지들은 서비스에서 서비스로 이동하고, 각 서비스의 큐 안에서 대기하게 되므로 전달 지연도 발생하게 된다.

그런데 새로운 기능을 추가하려면 어떨까? 충분히 상상할 수 있겠지만, 이들 모든 서비스 사이의 결합으로 인해 엄청난 양의 WSDL을 변경해야 하며, 변경에 영향받는 모든 것을 다시 배포해야 할 것이다.

서비스를 중심으로 구조화된 소프트웨어 시스템이 본질적으로 잘못된 것은 아니다.

W사의 실수는 SOA를 약속하는 일련의 도메인 객체 서비스를 너무 이른 시기에 채택했다. 이러한 실수로 인적 시간, 그것도 엄청난 양의 인적 시간에 따른 비용이 SOA의 소용돌이에 휩쓸려 떠내려갔다.

FitNesse의 성공 사례

로버트 C.마틴과 아들 미카는 2001년에 FitNesse를 만들기 시작했다.

워드 커닝햄이 인수 테스트 작성을 위해 만든 FIT도구를 기반으로 하는 간단한 위키 페이지를 만들려는 생각이었다.

이 당시에는 메이븐이 등장하기 전이었다. 그들은 그들이 만든 어떤 것도 jar파일을 둘 이상 다운로드하도록 만들어서는 안 된다는 점에서 단호했다.

초기에 내린 결정 중 하나는 FitNesse의 요구에 특화된, 그들만의 웹 서버를 작성하자는 것이었다.

수많은 웹 서버가 오픈 소스로 있었지만, 어떤 웹 프레임워크를 사용할지에 대한 결정을 훨씬 나중으로 연기할 수 있도록 해주었다.

초기에 내린 또 다른 결정은 데이터베이스에 대해 고민하지 말자는 것이었다.

어떤 데이터베이스를 사용하더라도 상관없는 형태로 설계함으로써 의도적으로 데이터베이스에 대한 결정을 미루었다.

데이터 접근 메서드들을 WikiPage라는 인터페이스에 두었다.

이들 메서드는 페이지를 찾고, 데이터를 가져오며, 저장하는 데 필요한 모든 기능을 제공했다.

물론 처음에는 이들 메서드를 구현하지 않았다.

실제로 석 달 동안은 그저 위키 텍스트를 HTML로 변환하는 작업만을 진행했다.

이 작업에서는 데이터 저장소가 전혀 필요하지 않았기에, MockWikiPage라는 이름의 클래스를 만들고, 그 안의 데이터 접근 메서드들은 단순히 스텁으로 남겨 두었다.

마침내 작성하려고 하는 기능에서 스텁만으로는 충분하지 않게 되었다.

WikiPage의 파생 클래스를 InMemortPage라는 이름으로 새롭게 만들었다. 이 파생 클래스는 위키 페이지의 해시 테이블을 관리하는 형태로 데이터 접근 메서드를 구현했고, 이 해시 테이블은 RAM에 유지되었다.

이 덕분에 그들은 기능들을 차례대로 작성할 수 있었다.

영속성을 구현해야 하는 시점이 되자, MySQL을 한 번 고민했지만, 단기적으로는 필요하지 않다는 결정을 내렸다. 해시 테이블을 플랫에 저장하는건 쉽기 때문이다.

이 클래스는 데이터 접근 관련 기능들을 단순히 플랫 파일을 기반으로 구현한 것에 지나지 않았다. 그런 후 우리는 또 다른 기능들을 계속해서 개발했다.

그들은 플랫 파일을 사용한 해결책이 적합했다는 결론에 도달했다.

다시 말해 MySQL과 관련된 아이디어는 완전히 폐기하기로 했다.

애초에 연기하기로 했던 결정을 완전히 내렸다.

어떤 고객 한명이 자신은 위키를 MySQL에 저장해야 한다고 말했다.

결정을 미룬 덕분에, 단 하루만에 파생 클래스를 하나 작성해서 시스템이 동작하게 만들었다.

그러나, 추가한 뒤로 아무도 사용하지 않았고, 다시 없앴다.

개발 초기에 그들은 업무 규칙과 데이터베이스 사이에 경계선을 그었다.

경계선을 통해 업무 규칙은 데이터 접근 메서드 외에는 데이터베이스에 관해서 어떤 것도 알지 못하게 되었다.

이러한 결정으로 더 나은 해결책이 보이면 방향을 바꿀 수 있었고, 다양한 시도도 가능했다.

경계선을 긋는 행위는 결정을 늦추고 연기하는 데 도움이 되었고, 궁극적으로는 시간을 엄청나게 절약해주었다.

어떻게 선을 그을까? 그리고 언제 그을까?

관련이 있는 것과 없는 것 사이에 선을 긋는다.

GUI는 업무 규칙과는 관련 없기 때문에, 이 둘 사이에는 반드시 선이 있어야 한다.

데이터베이스는 GUI와는 관련이 없으므로, 이 둘 사이에 선이 있어야 한다.

데이터베이스는 업무 규칙과 관련이 없으므로, 이 둘 사이에도 선이 있어야 한다.

그림 17.1을 보자. BusinessRulesDatabaseInterface를 사용하여 데이터를 로드하고 저장한다.

DatabaseAccessDatabaseInterface를 구현하며, Database를 실제로 조작하는 일을 맡는다.

그림 17.1

이 도표에서 클래스와 인터페이스는 상징적이다.

실제 애플리케이션에서는 업무 규칙과 관련된 수많은 클래스들, 데이터베이스 인터페이스와 관련된 수많은 클래스들, 그리고 데이터베이스 접근을 구현한 수많은 구현체가 존재한다.

그렇더라도 이 모두는 이 패턴을 거의 동일하게 따른다.

경계선은 어디에 있는가?

경계선은 상속 관계를 횡단하면서 DatabaseInterface 바로 아래에 그어진다.

그림 17.2

DatabaseAccess에서 출발하는 두 화살표에 주목하자. 이들 두 화살표는 DatabaseAccess 클래스로부터 바깥쪽으로 향한다. 즉, 이 도표에서 Database Access가 존재한다는 사실을 알고 있는 클래스는 없다는 뜻이다.

이제 조금 물러나서 보자. 많은 업무 규칙이 포함된 컴포넌트, 데이터베이스와 데이터베이스 접근 클래스를 포함하는 컴포넌트는 살펴보려고 한다.(그림 17.3)

 


화살표의 방향에 주목하자.

DatabaseBusinessRules에 대해 알고 있다.

BusinessRulesDatabase에 관해 알지 못한다.

 

이 선의 방향이 중요하다.

BusinessRules에게 있어 Database는 문제가 되지 않지만, DatabaseBusinessRules 없이는 존재할 수 없다는 사실을 이 방향을 통해 알 수 있다.

이러한 사실이 이상하게 보인다면 하나만 기억하자. Database 컴포넌트는 BusinessRules가 만들어낸 호출을 데이터베이스의 쿼리 언어로 변환하는 코드를 담고 있다.

BusinessRules에 대해 알고 있는 코드는 바로 이 변환 코드다.

 

두 컴포넌트 사이에 이러한 경계선을 그리고 화살표의 방향이 BusinessRules를 향하도록 만들었으므로, BusinessRules에서는 어떤 종류의 데이터베이스도 사용할 수 있음을 알 수 있다.

Database 컴포넌트는 다양한 구현체로 교체될 수 있으며, BusinessRules는 조금도 개의치 않는다.

 

데이터베이스는 오라클, MySQL, 카우치, Atomic 등으로 구현할 수 있고, 심지어 플랫 파일로도 구현할 수 있다.

업무 규칙은 전혀 개의치 않는다.

그리고 이 같은 사실은 데이터베이스에 대한 결정은 연기할 수 있으며, 데이터베이스를 결정하기에 앞서 업무 규칙을 먼저 작성하고 테스트하는 데 집중할 수 있음을 의미한다.

 

입력과 출력은?

우리는 시스템의 행위를 입출력이 지닌 행위적 측면에서 생각하는 경향이 있다.

예를 들어 비디오게임을 생각해보자.

이 경우 사용자 인경험은 인터페이스에 의해 좌우된다.

이러한 인터페이스 뒤에는 인터페이스를 조작하는 모델이 있음을 잊어버린다.

더 중요한 사실은 모델은 인터페이스를 전혀 필요로 하지 않는다는 점이다. 게임이 화면에 전혀 출력되지 않더라도 모델은 게임에서 발생되는 모든 이벤트를 모델링하면서 주어진 역할을 충실하게 수행한다.

 

인터페이스는 모델에게 있어 중요하지 않다. 중요한 것은 업무 규칙이다.

따라서 이번에도 마찬가지로 GUI와 BusinessRules 컴포넌트가 경계선에 의해 분할된다는 사실을 볼 수 있다.

(그림 17.4).

관련성이 낮은 컴포넌트가 관련성이 높은 컴포넌트에 의존한다는 사실을 다시 한번 볼 수 있다.

화살표는 어느 컴포넌트가 어떤 컴포넌트를 알고 있는지를, 그래서 어느 컴포넌트가 어느 컴포넌트를 신경 쓰는지를 보여준다.

GUI가 BusinessRules를 신경쓴다.

 

그림 17.4 GUI와 BusinessRules 컴포넌트 사이의 경계

 

경계와 화살표를 이와 같이 그릴 수 있으므로, GUI는 다른 종류의 인터페이스로 얼마든지 교체할 수 있으며 BusinessRules는 전혀 개의치 않는다는 사실을 알 수 있다.

 

플러그인 아키텍처

데이터베이스와 GUI에 대해 내린 두 가지 결정을 하나로 합쳐서 보면 컴포넌트 추가와 관련한 일종의 패턴이 만들어진다.

이 패턴은 시스템에서 서드파티 플러그인을 사용할 수 있게 한 바로 그 패턴과 동일하다.

 

사실 소프트웨어 개발 기술의 역사는 플러그인을 손쉽게 생성하여, 확장 가능하며 유지보수가 쉬운 시스템 아키텍처를 확립할 수 있게 만드는 방법에 대한 이야기다.

선택적이거나 또는 수많은 다양한 형태로 구현될 수 있는 나머지 컴포넌트로부터 핵심적인 업무 규칙은 분리되어 있고, 또한 독립적이다.(그림 17.5)

 

그림 17.5: 업무 규칙에 플러그인 형태로 결합하기

 

이 설계에서 사용자 인터페이스는 플러그인 형태로 고려되었기에, 수많은 종류의 사용자 인터페이스를 플러그인 형태로 연결할 수 있게 된다.

웹 기반일 수도 있고, 클라이언트 / 서버 기반이거나, SOA나 콘솔 기반, 또는 임의의 어떤 사용자 인터페이스 기술이라도 가능하다.

데이터베이스에도 동일하게 적용할 수 있다.

 

데이터베이스를 플러그인으로 다루기로 결정했기 때문에, 임의의 다양한 SQL 데이터베이스, NoSQL 데이터베이스, 파일 시스템 기반 데이터베이스, 또는 미래에 필요하리라 생각되는 임의의 어떤 종류의 데이터베이스 기술로도 대체할 수 있다.

이러한 교체 작업은 사소한 일은 아닐 것이다. 시스템의 초기 배포본이 웹 기반이었다면 클라이언트-서버 UI용 플러그인을 작성하는 것은 쉽지 않은 일이 될 수도 있다.

업무 규칙과 이 새로운 UI간의 통신 일부는 재작업해야 할 가능성이 높다.

그렇다 하더라도 플러그인 구조를 가정한 채 시작함으로써, 최소한 우리는 이러한 변경 작업을 현실성 있도록 만들었다.

 

플러그인에 대한 논의

우리는 특정 모듈이 나머지 모듈에 영향받지 않기를 바란다.

예를 들어 누군가가 웹 페이지의 포맷을 변경하거나 데이터베이스 스키마를 변경하더라도 업무 규칙은 깨지지 않기를 바란다. 마찬가지로 시스템에서 한 부분이 변경되더라도 관련 없는 나머지 부분이 망가지길 원치 않는다.

개발 중인 시스템에서 이러한 종류의 취약성이 드러나지 않기를 바란다.

 

시스템을 플러그인 아키텍처로 배치함으로써 변경이 전파될 수 없는 방화벽을 생성할 수 있다.

GUI가 업무 규칙에 플러그인 형태로 연결되면 GUI에서 발생한 변경은 절대로 업무 규칙에 영향을 미칠 수 없다.

 

경계는 변경의 축이 있는 지점에 그어진다.

경계의 한쪽에 위치한 컴포넌트는 경계 반대편의 컴포넌트와는 다른 속도로, 그리고 다른 이유로 변경된다.

GUI는 업무 규칙과는 다른 시점에 다른 속도로 변경되므로, 둘 사이에는 반드시 경계가 필요하다.

업무 규칙은 의존성 주입 프레임워크와는 다른 시점에 그리고 다른 이유로 변경되므로, 둘 사이에도 반드시 경계가 필요하다.

 

이 역시도 순전히 단일 책임 원칙에 해당한다.

단일 책임 원칙은 어디에 경계를 그어야 할지를 알려준다.

 

결론

경계선을 그리려면 먼저 시스템을 컴포넌트 단위로 분할해야 한다.

일부 컴포넌트는 핵심 업무 규칙에 해당한다.

나머지 컴포넌트는 플러그인으로, 핵심 업무와는 직접적인 관련이 없지만 필수 기능을 포함한다.

그런 다음 컴포넌트 사이의 화살표가 특정 방향, 즉 핵심 업무를 향하도록 이들 컴포넌트의 소스를 배치한다.

 

이는 의존성 역전 원칙과 안정된 추상화 원칙을 응용한 것임을 눈치챌 수 있어야 한다.

의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배치된다.

728x90
반응형

'개발 > Clean Architecture' 카테고리의 다른 글

5-5. 정책과 수준  (0) 2023.11.26
5-4. 경계 해부학  (0) 2023.11.24
5-2. 독립성  (0) 2023.11.20
5-1. 아키텍처란?  (0) 2023.11.18
4-3. 컴포넌트 결합  (0) 2023.11.16