Featured image of post 16. 독립성

16. 독립성

5부 - 아키텍처

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

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

유스케이스

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

실제로 아키텍트의 최우선 관심사는 유스케이스이며, 아키텍처에서도 유스케이스가 최우선이다. 따라서 아키텍처는 반드시 유스케이스를 지원해야한다.

아키텍처는 시스템의 행위 차제에는 큰 영향을 주지 않으며, 행위와 관련하여 아키텍처가 열어둘 수 있는 선택사항의 거의 없다.

하지만, 아키텍처의 시스템의 행위에 대한 직접적인 영향력과 별개로 좋은 아키텍처는 시스템의 행위를 지원할 수 있다.

  • 행위를 명확히 하고 외부로 드러낸다.
  • 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만든다.

좋은 아키텍처를 가진다면 시스템의 유스케이스는 시스템 구조 자체에서 한눈에 드러난다.

  • 이러한 시스템의 행위일급 요소 first-class element이며 시스템의 최상위 수준에서 알아볼 수 있으므로, 개발자가 일일이 찾아 헤매이지 않아도 된다.
  • 이들 요소는 클래스이거나 함수 또는 모듈러서 아키텍처 내에서 핵심적인 자리를 차지할 뿐만 아니라, 자신의 기능을 분명하게 설명하는 이름을 가져야한다.

운영

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

예시

  • 시스템이 초당 100,000명의 고객을 처리해야 한다면, 아키텍처는 이 요구와 관련된 각 유스케이스에 걸맞은 처리량과 응답시간을 보장해야 한다.
  • 시스템에서 수 밀리초 안에 3차원의 빅데이터 테이블에 질의해야 한다면, 반드시 이러한 운영 작업을 허용할 수 있는 형태로 아키텍처를 구조화해야한다.

예시와 같이 형태를 지원한다는 말은 다양한 의미를 지닌다.

  • 시스템의 처리 요소를 일련의 작은 서비스들로 배열하여, 서로 다른 많은 서버에서 병렬로 실행할 수 있는 시스템
  • 경량의 수많은 스레드가 단일 프로세서에서 같은 주소 공간을 공유하는 시스템
  • 독립된 주소 공간에서 실행되는 소수의 프로세스만으로도 충분한 시스템
  • 단일 프로세스에서 실행되는 단순한 모노리틱 프로그램

위와 같은 운영 관점의 아키텍처 결정은 뛰어난 이키텍트라면 열어두어야 하는 선택 사항 중의 하나다.

아키텍처에서 각 컴포넌트를 적절히 격리하여 유지하고 컴포넌트 간 통신 방식을 특정 형태로 제한하지 않는다면, 시간이 지나 운영에 필요한 요구사항이 바뀌더라도 스레드, 프로세스, 서비스로 구성된 기술 스펙트럼 사이를 전환하는 일이 훨씬 쉬워질 것이다.

개발

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

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

  • 많은 팀으로 구성되며 관심사가 다양한 조직에서 어떤 시스템을 개발해야 한다면, 각 팀이 독립적으로 행동하기 편한 아키텍처를 반드시 확보하여 개발하는 동안 팀들이 서로 방해하지 않도록 해야 한다.
  • 잘 격리되어 독립적으로 개발 가능한 컴포넌트 단위로 시스템을 분할 할 수 있어야 한다.

배포

아키텍처는 배포 용이성(즉각적인 배포 immediate deployment)을 결정하는 데 중요한 역할을 한다.

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

  • 수십 개의 작은 설정 스크립트나 속성 파일을 약간씩 수정하는 방식을 사용하지 않는다.
  • 꼭 필요한 디렉터리나 파일을 수작업으로 생성하게 내버려 두지 않는다.

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

마스터 컴포넌트는 시스템 전체를 하나로 묶고, 각 컴포넌트를 올바르게 구동하고 통합하고 관리해야 한다.

선택사항 열어놓기

좋은 아키텍처는 컴포넌트 구조와 관련된 이 관심사들 사이에서 균형을 맞추고, 각 관심사 모두를 만족시켜야 하지만, 현실에서는 이러한 균형을 잡기가 매우 어렵다.

  • 모든 유스케이스를 알 수는 없다.
  • 운영하는 데 따르는 제약사항, 팀 구조, 배포 요구사항을 알지 못한다.
  • 이러한 사항들을 알고 있더라도, 시스템이 생명주기의 단계를 하나씩 거쳐감에 따라 이 사항들도 반드시 변한다.

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

이러한 변화속에서도 몇몇 아키텍처 원칙을 구현하는 비용이 비교적 비싸지 않으며, 이를 통해 관심사들 사이에서 균형을 잡는데 도움이 된다는 사실은 변하지 않는다.

  • 균형을 맞추려는 목표점을 명확히 그릴 수 없는 경우에도 관심사들 사이에서 균형을 잡는 데 도움이 된다.
  • 시스템을 제대로 격리된 컴포넌트 단위로 분할할 때 도움이 된다.
  • 이를 통해 선택사항을 가능 한 많이, 가능한 오랫동안 열어 둘 수 있게 해준다.

좋은 아키텍처는 선택사항을 열어 둠으로써, 향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.

계층 결합 분리

UI, 애플리케이션 자체와 밀접한 업무 규칙과, 도메인에 밀접한 업무 규칙, 기술적인 세부사항 등은 각자 다른 속도로, 다른 이유로 변경되므로 서로 분리하고, 독립적으로 변경할 수 있도록 만들어야만 한다.

업무 규칙은 그 자체가 애플리케이션과 밀접한 관련이 있거나, 혹은 더 범용적일 수 있는데, 아키텍트는 이들을 시스템의 나머지 부분으로부터 분리하여 독립적으로 변경할 수 있도록 해야한다.

  • 입력 필드 유효성 검사는 애플리케이션 자체와 밀접하게 관련된 업무 규칙이다.
  • 계좌의 이자 계산이나 재고품 비교는 업무 도메인에 더 밀접하게 연관된 업무 규칙이다.

이러한 계층의 예로는 UI, 애플리케이션에 특화된 업무 규칙, 애플리케이션과는 독립접인 업무 규칙, 데이터베이스와 같은 기술적인 세부사항등이 있다.

유스케이스 결합 분리

유스케이스 자체도 서로 다른 이유로 변경될 수 있다.

  • 예) 주문 입력 시스템의 주문 추가 유스케이스와 주문 삭제 유스케이스

유스케이스는 시스템의 수평적인 계층을 가르지르도록 자른 수직으로 좁다란 조각이기도 하다.

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

따라서 시스템을 수평적 계층으로 분할하면서 동시에 해당 계층을 가로지르는 얇은 수직적인 유스케이스로 시스템을 분할할 수 있다.

이와 같이 수직, 수평 결합을 동시에 분리하여 시스템의 맨 아래 계층까지 수직으로 내려가며 유스케이스들이 각 계층에서 서로 겹치지 않게 한다.

서로 다른 이유로 변경되는 요소들의 결합을 분리하면 기존 요소에 지장을 주지 않고도 새로운 유스케이스를 계속해서 추가할 수 있다.

또한 유스케이스를 뒷받침하는 서로 다른 관점(aspect)을 사용하게 되면, 새로운 유스케이스를 추가하더라도 기존 유스케이스에 영향을 주는 일은 거의 없을 것이다.

개발 독립성

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

기능 팀, 컴포넌트 팀, 계층 팀, 혹은 다른 형태의 팀이라도, 계층과 유스케이스의 결합이 분리되는 한 시스템의 아키텍처는 그 팀 구조를 뒷받침해 줄 것이다.

배포 톡립성

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

결합을 제대로 분리했다면 운영 중인 시스템에서도 계층과 유스케이스를 교체할 수 있다.

가짜 중복

아키텍트는 중복에 대한 공포로부터 발생하는 함정에 빠지곤 한다.

소프트웨어에서 중복은 일반적으로 나쁜 것이나, 중복에도 여러 종류가 있다.

  • 진짜 중복
    • 인스턴스가 변경되면, 동일한 변경을 그 인스턴스의 모든 복사본에 반드시 적용해야한다.
  • 가짜 중복: 거짓된 또는 우발적인 중복
    • 중복으로 보이는 두 코드 영역이 각자의 경로로 발전한다면(서로 다른 속도와 다른 이유로 변경된다면) 이 코드는 중복이 아니다.

의도적으로 만든 중복(비슷하지만 다른 방향으로 발전할 것이 예상되는)우발적 중복을 통합한다면 나중에 코드를 다시 분리하느라 큰 수고를 감수해야 한다.

유스케이스를 수직으로 분리할 때 이러한 문제와 마주치는 경우가 많으므로, 중복이 진짜 중복인지 확인하고 통합해야한다.

계층을 수평으로 분리하는 경우, 특정 데이터베이스 레코드의 데이터 구조가 특정 화면의 데이터 구조와 상당히 비슷한 경우를 자주 볼 수 있는데, 이때 데이터베이스 레코드와 동일한 형태의 뷰 모델을 만들어서 각 항목을 복사하는 방식을 선택해야한다.

결합 분리 모드

위와 같이 결합을 분리하면 운영 관점에서 여러가지 장점을 살펴볼 수 있다.

유스케이스에서 서로 다른 관점이 분리되었다면,

  • 높은 처리량을 보장해야 하는 유스케이스와 낮은 처리량으로도 충분한 유스케이스는 이미 분리되어 있을 가능성이 높다.
  • UI와 데이터베이스가 업무 규칙과 분리되어 있다면, UI와 데이터베이스는 업무 규칙과는 다른 서버에서 실행될 수 있다.
  • 높은 대역폭을 요구하는 유스케이스는 여러 서버로 복제하여 실행할 수 있다.

유스케이스를 위해 수행하는 그 작업들(분리된 것들)은 운영에도 도움이 될 수 있다.

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

  • 분리된 컴포넌트를 서로 다른 서버에서 실행해야 하는 상황: MSA 등

때때로는 컴포넌트를 서비스 수준까지도 분리해야 한다.

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

계층과 유스케이스의 결합을 분리하는 방법은 다양하다.

  • 소스 수준 분리 모드
    • 소스 코드 모듈 사이의 의존성을 제어할 수 있다.
    • 하나의 모듈이 변하더라도 다른 모듈을 변경하거나 재컴파일하지 않도록 만들 수 있다.
    • 모노리틱 구조
  • 배포 수준 분리 모드
    • .jar, DLL, 공유 라이브러리와 같이 배포 가능한 단위들 사이의 의존성을 제어할 수 있다.
    • 소스 코드가 변하더라도 다른 모듈을 재빌드하거나 재배포하지 않도록 만들 수 있다.
    • 많은 컴포넌트가 같은 주소 공간에 상주하며, 단순한 함수 호출을 통해 통신할 수 있다.
    • 어떤 컴포넌트는 동일한 프로세스의 다른 프로세스에 상주하고, 프로세스 간 통신, 소켓, 공유 메모리를 통해 통신할 수 있다.
    • 결합이 분리된 컴포넌트가 독립적으로 배포할 수 있는 단위로 분할되어 있다.
  • 서비스 수준 분리 모드
    • 의존하는 수준을 데이터 구조 단위까지 낮추고, 네트워크 패킷을 통해서만 통신하도록 만들 수 있다.
    • 모든 실행 가능한 단위는 소스와 바이너리 변경에 대해 서로 완전히 독립적이게 된다.
    • 마이크로서비스, 서비스

프로젝트 초기 단계는 어떤 모드가 최선인지 알기 어려우며, 프로젝트가 성숙해갈수록 최적인 모드가 달라질 수 있다.

좋은 아키텍처는 시스템이 모노리틱 구조로 태어나서 단일 파일로 배포되더라도,이후에는 독립적으로 배포 가능한 단위들의 집합으로 성장하고, 또 독립적인 서비스나 마이크로서비스 수준까지 성장할 수 있도록 만들어져야 한다.(반대도 마찬가지)

좋은 아키텍처는 결합 분리 모드를 선택사항으로 남겨두어서 배포 규모에 다라 가장 적합한 모드를 선택해 사용할 수 있게 만들어 준다.

결론

시스템의 결합 분리 모드는 시간이 지나면서 바뀌기 쉬우며, 뛰어난 아키텍트라면 이러한 변경을 예측하여 큰 무리 없이 반영할 수 있도록 만들어야한다.