[Clean Architecture] - 경계

휴먼스케이프

안녕하세요. Humanscape Software Engineer, Hugh입니다.

오늘은 Clean Architecture 책의 17, 18, 19장에 해당하는 내용을 알아보고자 합니다.

17장 경계: 선 긋기

소프트웨어 아키텍처는 선을 긋는 기술이며, 경계(boundary)는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는 것 입니다. 이 선들은 의사결정을 적시적소에 하는데 도움을 줍니다.

예를 들어 시스템의 업무 요구사항, 유스케이스를 결정하는데는 프레임워크, 데이터베이스 등의 대한 결정은 이와 아무런 관련이 없으므로 나중에 결정해도 무방합니다. 이 것을 선을 긋는다고 표현합니다.

[선 긋기 안 좋은 예 1]

데스크탑 GUI 애플리케이션을 보유한 회사가 1990년대 후반 웹이 대세가 되면서, 웹 애플리케이션을 확보해야 했습니다. 그래서 다수의 자바 개발자들을 채용했고, 자바 진영 사람은 머릿속에서 서버 팜(server farm)이 춤추는 이상을 꿈꾸었기에 3-티어(GUI를 위한 서버, 미들웨어 서버, 데이터베이스 서버)로 구성된 리치 아키텍처를 채택했고, 서버 팜을 통해 분산하고자 했습니다.

티어간 메서드 호출은 객체로 변환하여, 직렬화 한 후, 회선을 통해 마샬링됩니다. 이 구조에서 기존 레코드에 새로운 필드를 추가한다고 가정해보면, 세 티어에 있는 클래스 모두와 티어 간 메시지 다수에 추가해야하고, 데이터는 양방향으로 이동하므로 메시지 프로토콜은 반드시 네 개를 설계, 각 프로토콜은 송신부와 수신부를 가지므로 프로토콜 핸들러는 총 여덟개가 필요하고, 세 개의 실행 파일이 빌드되어야 합니다. 이들은 개발하는 동안 서버 팜을 확보할 수 없었고, 단일 서버에서 세 개의 프로세스로 실행하는 방법으로 했습니다. 역설적이게도 클라이언트는 서버 팜을 필요로 하지 않고, 단일 서버를 필요로 하는 클라이언트 뿐이었다고 합니다.

이 사례는 아키텍트가 너무 이르게 결정을 내림으로써 개발 비용을 엄청나게 가중시킨 사례입니다.

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

관련이 있는 것과 없는 것 사이에 선을 긋습니다. 예를 들어 GUI는 업무 규칙과는 관련이 없기 때문에 둘 사이에 선을 긋습니다. 데이터 베이스는 GUI와는 관련이 없으므로 이 둘 사이에도 반드시 선이 있어야 합니다. 데이터베이스는 업무 규칙과 관련이 없으므로 이 둘 사이에도 선이 있어야 합니다. 데이터베이스와 업무 규칙과는 서로 떼어놓을 수 없는 관계라고 생각하는 사람도 많을텐데 이는 잘못된 생각입니다.

<선의 적절한 위치>

업무 규칙은 스키마, 쿼리 언어, 또는 데이터 베이스와 관련된 세부사항에 대해 어떤 것도 알아서는 안됩니다. 업무 규칙이 알아야 할 것은 단순히 데이터를 가져오고 저장할 때 사용할 수 있다는 것이 전부입니다. 이러한 함수 집합을 통해서 우리는 데이터베이스를 인터페이스 뒤로 숨길 수 있습니다. BusinessRules는 DatabaseInterface를 사용하여 데이터를 로드하고 저장합니다. DatabaseAccess는 DatabaseInterface를 구현하며 Database를 실제로 조작하는 일을 맡습니다. 실제 애플리케이션의 구현체는 이 패턴을 거의 동일하게 따르고, 이 경우 경계선은 DatabaseInterface와 DatabaseAccess 사이에 그어집니다. 이 구조에서 화살표의 방향을 잘 보면 DatabaseAccess의 존재를 아는 클래스는 없습니다.

[플러그인 아키텍처]

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

<업무 규칙에 플러그인 형태로 연결하기>

이 설계에서 사용자 인터페이스와 데이터베이스는 플러그인 형태로 연결할 수 있게됩니다. 웹, 콘솔 또는 임의의 어떤 사용자 인터페이스 기술이라도 가능합니다. 데이터베이스 역시 어떤 종류의 기술로도 대체할 수 있습니다.

[플러그인에 대한 논의]

ReSharper와 VS(Visual Studio)의 관계를 생각해봅니다. ReSharper의 소스코드는 VS의 소스코드에 의존합니다. 따라서 ReSharper는 VS에 영향을 끼칠 수 없지만, VS는 언제든지 ReSharper를 무력화시킬 수 있습니다. 이 관계는 상당히 비 대칭적이며 우리가 시스템에서 갖추고자 하는 바로 그런 관계입니다.

예를 들어 웹 페이지의 포맷을 변경하거나 데이터베이스 스키마를 변경하더라도 업무 규칙은 깨지지 않아야 합니다. 마찬가지로 시스템에서 한 부분이 변경되더라도 관련 없는 나머지 부분이 망가지는 일이 없어야 합니다. 이 부분은 단일 책임 원칙에 해당하고, 단일 책임 원칙은 어디에 선을 그어야 할지를 알려줍니다.

[결론]

선을 그리려면 먼저 시스템을 컴포넌트 단위로 분할해야합니다. 컴포넌트는 핵심 업무, 플러그인, 핵심 업무와는 직접적인 관련이 없지만 필수 기능을 포함합니다. 그런 다음 컴포넌트 사이의 화살표가 특정 방향, 즉 핵심 업무를 향하도록 이들 컴포넌트의 소스를 배치합니다.

18장 경계 해부학

시스템 아키텍처는 소프트웨어 컴포넌트와 그 컴포넌트들을 분리하는 경계에 의해 정의됩니다. 경계는 다양한 형태로 나타나는데 이번 장에서는 가장 흔한 형태 몇 가지를 살펴봅니다.

[경계 횡단하기]

적절한 위치에서 경계를 횡단하게 하는 비결은 소스 코드 의존성 관리에 있습니다. 소스 코드 모듈 하나가 변경되면 의존하는 다른 소스 코드 모듈도 변경하거나 다시 컴파일해서 새로 배포해야 할지도 모릅니다. 경계는 이러한 변경이 전파되는 것을 막는 방화벽 역할을 합니다.

[두려운 단일체]

아키텍처 경계 중에서 가장 단순하며 가장 흔한 형태는 물리적으로 엄격하게 구분되지 않는 형태입니다. 이 형태에서는 함수와 데이터가 단일 프로세서의 같은 주소 공간을 공유합니다. 따라서 배포관점에서 보면 소위 단일체(monolith)라고 불리는 단일 실행 파일에 지나지 않기때문에 경계가 드러나지 않습니다.

그렇다고 해서 경계 자체가 무의미 하다는 것은 아닙니다. 이러한 경우 아키텍처는 그 안에 포함된 다양한 컴포넌트를 개발하고 바이너리로 만드는 과정을 독립적으로 수행할 수 있게 하는 일은 대단히 가치 있습니다. 이러한 아키텍처는 거의 모든 경우에 특정한 동적 다형성에 의존하여 내부 의존성을 관리할 수 있습니다.

바로 이 때문에 최근 수십 년 동안 객체 지향 개발이 아주 중요한 패러다임이 될 수 있었습니다. 객체 지향, 다형성에 해당하는 메커니즘이 없었다면 아키텍트는 결합도를 적절히 분리하기 위해 함수를 가리키는 포인터라는 위험한 옛 관행에 기대야만 했을 것입니다.

가장 단순한 형태의 경계 횡단은 저수준 클라이언트에서 고수준 서비스로 향하는 함수 호출입니다. 이 경우 런타임과 컴파일 타임 의존성 모두 같은 방향을 향합니다.

다음 그림에서 제어흐름은 왼쪽에서 오른쪽으로 경계를 횡단합니다.

<제어흐름은 경계를 횡단할 때 저수준에서 고수준으로 향한다.>

Client는 Service의 함수 f()를 호출하고, Data 인스턴스를 전달합니다. 주목할 점은 Data에 대한 정의가 호출되는 쪽에 위치한다는 사실입니다.

고수준 클라이언트가 저수준 서비스를 호출해야 한다면 동적 다형성을 사용하여 제어흐름과 반대 방향으로 의존성을 역전시킬 수 있습니다. 이렇게 되면 의존성은 컴파일타임 의존성과 반대가 됩니다.

<제어흐름과는 반대로 경계를 횡단한다.>

정적 링크된 단일체 구조의 실행 파일이라도 이처럼 규칙적인 방식으로 구조를 분리하면 개발, 테스트, 배포하는 작업에 큰 도움이 됩니다. 단일체에서 컴포넌트 간 통신은 전형적 함수 호출에 지나지 않기 때문에 값이 쌉니다.

[배포형 컴포넌트]

아키텍처의 경계가 물리적으로 드러날 수도 있는데 그중 가장 단순한 형태는 동적 링크 라이브러리(.NET DLL, 자바 jar 파일, 루비 GEM, 유닉스 공유 라이브러리 등)입니다. 컴포넌트를 이 형태로 배포하면 따로 컴파일 없이 사용할 수 있습니다. 배포형 컴포넌트는 단일체와 마찬가지로 동일한 프로세서와 주소공간에 위치하며 컴포넌트 간 의존성을 관리하는 전략도 단일체와 같습니다.

[스레드]

단일체와 배포형 컴포넌트 모두 스레드를 활용할 수 있습니다. 스레드는 아키텍처 경계도 아니며 배포 경계도 아닙니다. 스레드는 실행 계획과 순서를 체계화 하는 방법에 가깝습니다.

[로컬 프로세스]

강한 물리적 형태를 띠는 경계로는 로컬 프로세스가 있습니다. 로컬 프로세스는 주로 명령행이나 그와 유사한 시스템 호출을 통해 생성됩니다. 로컬 프로세스는 정적으로 링크된 단일체이거나 동적으로 링크된 여러 개의 컴포넌트로 구성될 수 있습니다.

로컬 프로세스 간 분리 전략은 단일체나 바이너리 컴포넌트의 경우와 동일합니다. 소스 코드 의존성의 화살표는 단일체나 바이너리 컴포넌트와 동일한 방향으로 횡단합니다. 즉 항상 고수준 컴포넌트를 향합니다.

[서비스]

물리적인 형태를 띠는 가장 강력한 경계는 바로 서비스입니다. 서비스는 프로세스로, 일반적으로 명령행 또는 그와 동등한 시스템 호출을 통해 구성됩니다.

서로 통신하는 두 서비스는 물리적으로 동일한 프로세서나 멀티코어에서 동작할 수도 있고, 아닐 수도 있습니다. 서비스들은 모든 통신이 네트워크를 통해 이뤄진다고 가정합니다.

서비스 경계를 지나는 통신은 단일체의 함수 호출에 비해 매우 느리기때문에 빈번하게 통신하는 일을 피해야 합니다. 이를 제외하고는 마찬가지로 저수준 서비스는 항상 고수준 서비스에 플러그인 되여야 합니다.

[결론]

단일체를 제외한 대다수의 시스템은 한 가지 이상의 경계 전략을 사용합니다. 대체로 한 시스템 안에서도 통신이 빈번한 로컬 경계와 지연을 중요하게 고려해야 하는 경계가 혼합되어 있음을 의미합니다.

19장 정책과 수준

소프트웨어에서 시스템이란 정책을 기술한 것입니다. 대다수의 주요 시스템에서 하나의 정책은 이 정책을 서술하는 여러 개의 조그만 정책들로 쪼갤 수 있습니다. 소프트웨어 아키텍처를 개발하는 기술에는 이러한 정책을 신중하게 분리하고, 정책이 변경되는 양상에 따라 정책을 재편성 하는 일도 포함됩니다.

흔히 아키텍처 개발은 재편성된 컴포넌트들을 비순환 방향 그래프로 구성하는 기술을 포함합니다. 그래프에서 node는 동일한 수준의 정책을 포함하는 컴포넌트, edge는 컴포넌트 사이의 의존성을 나타냅니다.

좋은 아키텍처라면 각 컴포넌트를 연결할 때 의존성의 방향이 저수준 컴포넌트가 고수준 컴포넌트를 의존하도록 설계되어야 합니다.

[수준]

수준(Level)을 정의하자면 ‘입력과 출력까지의 거리’ 입니다. 시스템의 입력과 출력 모두로부터 멀리 위치할수록 정책의 수준은 높아집니다. 입력과 출력을 다루는 정책이라면 시스템에서 최하위 수준에 위치합니다.

<간단한 암호화 프로그램>

다음 그림에서 입력과 출력을 담당하는 컴포넌트인 문자 읽기와 쓰기는 저수준 컴포넌트이고, 입력과 출력을 담당하는 컴포넌트와 멀리 위치한 번역 컴포넌트는 은 고수준 컴포넌트 입니다. 데이터 흐름은 굽은 실선, 소스코드 의존성은 곧은 점선으로 표시 했습니다. 소스 코드 의존성은 그 수준에 따라 결합되어야 하며 데이터 흐름을 기준으로 결합되어서는 안 됩니다.

자칫하면 아래와 같은 잘 못 된 아키텍처가 설계됩니다.

function encrypt() {
  while (true)
    writeChar(translate(readChar()));
}

고수준인 encrypt 함수가 저수준인 readChar 와 writeChar 함수에 의존하기 때문입니다. 다음 클래스 다이어그램은 이 시스템의 아키텍처를 개선해본 모습입니다.

<더 나은 아키텍처를 보여주는 클래스 다이어그램>

점선은 경계를 의미합니다. 이 경계를 횡단하는 의존성은 모두 경계 안쪽으로 향합니다. 이 경계로 묶인 영역이 이 시스템에서 최고 수준의 구성요소 입니다.

고수준의 암호화 정책을 저수준의 입력/출력 정책으로 부터 분리시켰기 때문에, 입력과 출력에 변화가 생기더라도 암호화 정책은 영향을 받지 않습니다.

정책을 컴포넌트로 묶는 기준은 정책이 변경되는 방식에 달려있습니다. 동일한 시점에 변경되는 정책은 함께 묶입니다.

감사합니다.

Get to know us better! Join our official channels below.

Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io

기업문화 엿볼 때, 더팀스

로그인

/