시스템 아키텍처는 일련의 SW 컴포넌트와 그 컴포넌트들을 분리하는 경계에 의해 정의된다.
이러한 형태 중 가장 흔한 몇 가지를 살펴보려고 한다.
경계 횡단하기
"런타임에 경계를 횡단한다" 함은 그저 경계 한쪽에 있는 기능에서 반대편 기능을 호출하여 데이터를 전달하는 일에 불과하다.
적절한 위치에서 경계를 횡단하게 하는 비결은 소스 코드 의존성 관리에 있다.
-> 소스 코드 모듈 하나가 변경되면, 이에 의존하는 다른 소스 코드 모듈도 변경해야할 수도 있기 떄문이다.
두려운 단일체
아키텍처 경계 중에서 가장 단순하며 가장 흔한 형태는 물리적으로 엄격히 구분되지 않는 형태다.
이 형태에서는 함수와 데이터가 단일 프로세서에서 같은 주소 공간을 공유하며 그저 나름의 규칙에 따라 분리되어 있을 뿐이다.
이전 장까지는 소스 수준 분리 모드라고 불렀다.
배포 관점에서 보면 이는 소위 단일체(모노리틱)라고 불리는 단일 실행 파일에 지나지 않는다.
이처럼 배포 관점에서 볼 때 단일체는 경계가 드러나지 않는다.
-> 단일체는 컴포넌트 수준으로 분리되지 않으므로, 배포할 때 개별 컴포넌트를 배포하는 대신 하나의 파일을 배포한다.
그렇다고 단일체에 경계가 없는 것은 아니다.
-> 단일 실행 파일을 만들더라도, 그 안에 다양한 컴포넌트를 개발하는 과정을 독립적으로 수행할 수 있게 한다.
이러한 아키텍처는 거의 모든 경우에 특정한 동적 다형성에 의존하여 내부 의존성을 관리한다.
이 때문에 OOP가 중요한 패러다임이 될 수 있었다.
OOP, 다형성에 해당하는 메커니즘이 없었다면, 결합도를 분리하기 위해 함수를 가리키는 포인터라는 위험한 방법을 썼을 것이다.
가장 단순한 형태의 경계 횡단은
- 저수준 클라이언트에서 고수준 서비스로 향하는 함수 호출이다.
이 경우 런타임 의존성과 컴파일타임 의존성은 모두 같은 방향, 즉 저수준 컴포넌트에서 고수준 컴포넌트로 향한다.
그림의 제어흐름은 왼쪽에서 오른쪽으로 횡단한다.
Client는 Service의 함수 f()를 호출한다. 이때 Client는 Data 인스턴스를 전달한다.
Data는 함수의 인자로 전달할 수도 있고, 더 정교한 다름 기법을 통해 전달할 수도 있다.
주목할 점은 경계에서 호출되는 쪽에 Data에 대한 정의가 위치한다는 사실이다.
고수준 클라이언트가 저수준 서비스를 호출해야 한다면 동적 다형성을 사용하여 제어흐름과는 반대 방향으로 의존성을 역전시킬 수 있다.
이렇게 하면 런타임 의존성은 컴파일 타임 의존성과는 반대가 된다.
제어흐름은 이전과 마찬가지로 읜쪽에서 오른쪽으로 경계를 횡단한다.
고수준 Client는 Service 인터페이스를 통해 저수준인 ServiceImpl의 함수 f()를 호출한다.
주목할 점은 경계를 횡단할 때 의존성은 모두 오른쪽에서 왼쪽으로, 즉 고수준 컴포넌트를 향한다는 점이다.
또한 데이터 구조의 정의가 호출하는 쪽에 위치한다는 점도 주목하자.
모노리틱 구조도 이처럼 규칙적인 방식으로 구조를 분리하면 프로젝트를 개발, 테스트, 배포하는 작업에 큰 도움이 된다.
팀들은 서로의 영역에 침범하지 않은 채 자신만의 컴포넌트를 독립적으로 작업할 수 있다.
단일체에서의 컴포넌트 간 통신은 값싸다. 함수 호출로 이뤄지기 때문이다.
배포형 컴포넌트
아키텍처의 경계가 물리적으로 드러날 수도 있는데 그중 가장 단순한 형태는 동적 링크 라이브러리다.
자바 jar 파일, 루비 젬, .NET DLL 등이 있다.
이 형태로 배포하면 따로 컴파일하지 않고 바로 사용할 수 있다.
배포 과정만 다를 뿐, 배포 수준의 컴포넌트는 단일체와 동일하다.
함수 호출을 사용하여 통신한다.
스레드
단일체와 배포형 컴포넌트는 모두 스레드를 활용할 수 있다.
스레드는 아키텍처 경계도 아니며 배포 단위도 아니다.
스레드는 실행 계획과 순서를 체계화하는 방법에 가깝다.
로컬 프로세스
훨씬 강한 물리적 형태를 띠는 아키텍처 경계로는 로컬 프로세스가 있다.
로컬 프로세스들은 동일한 프로세서 또는 하나의 멀티코어 시스템에 속한 여러 프로세서들에서 실행되지만,
각각이 독립된 주소 공간에서 실행된다.
종종 공유 메모리 파티션을 사용하지만, 일반적으로는 메모리 보호를 위해 공유하지 못하게 한다.
대게 로컬 프로세스는 소켓, 메일박스, 메세지 큐와 같이 OS에서 제공하는 통신 기능을 이용한다.
로컬 프로세스 간 분리 전략도 앞과 같다.
따라서 로컬 프로세스에서는 고수준 프로세스의 소스 코드가 저수준 프로세스의 이름, 물리 주소, 레지스트리 조회 키를 포함해서는 안된다.
경계 횡단은 OS 호출, Context Switching 등이 있고, 비싼 작업이다.
서비스
물리적인 형태를 띠는 가장 강력한 경계는 바로 서비스다.
서비스는 프로세스로 일반적으로 명령행 또는 그와 동등한 시스템 호출을 통해 구동된다.
서비스는 자신의 물리적 위치에 구애받지 않는다.
서로 통신하는 두 서비스는 물리적으로 동일한 프로세서나 멀티코어에서 동작할 수도 있고, 아닐 수도 있다.
서비스들은 모두 네트워크를 통해 이뤄진다고 가정한다.
서비스 경계를 지나는 통신은 함수 호출에 비해 매우 느리다. -> 네트워크를 사용하므로
이 수준의 통신에서는 지연에 따른 문제를 고수준에서 처리할 수 있어야 한다.
결론
단일체를 제외한 대다수의 시스템은 한 가지 이상의 경계 전략을 사용한다.
서비스 경계를 활용하는 시스템이라면 로컬 프로세스 경계도 일부 포함하고 있을 수 있다.
실제로 서비스는 상호작용하는 일련의 로컬 프로세스 퍼사드에 불과할 때가 많다.
또한 개별 서비스 또는 로컬 프로세스는 언제나 소스 코드 컴포넌트로 구성된 단일체이거나, 동적으로 링크된 배포형 컴포넌트들의 집합이다
즉, 대체로 한 시스템 안에서도 통신이 빈번한 로컬 경계와 지연을 중요하게 고려해야 하는 경계가 혼합되어 있다.
'스터디 > 클린아키텍처' 카테고리의 다른 글
[Clean Architecture] 클린 아키텍처(독립성) - 13 (0) | 2021.04.05 |
---|---|
[Clean Architecture] 클린 아키텍처(아키텍처란?) - 12 (0) | 2021.04.05 |
[Clean Architecture] 클린 아키텍처(DIP : 의존성 역전 원칙) - 11 (0) | 2021.04.02 |
[Clean Architecture] 클린 아키텍처(ISP : 인터페이스 분리 원칙) - 10 (0) | 2021.04.02 |
[Clean Architecture] 클린 아키텍처(LSP - 리스코프 치환 원칙) - 9 (0) | 2021.04.01 |