5부. 아키텍처2
Last updated
Last updated
소프트웨어 아키텍처 : 시스템 요소를 분리하는 경계를 긋는 기술
경계 : 소프트웨어 요소를 서로 분리한다.
경계를 긋는 행위의 목표
인적 자원에 드는 비용 최소화
인적 자원의 비용을 높이는 요인 중 결합(coupling)이 있으며, 특히 너무 이른 결정에 의한 결합이 비용을 높이게 된다.
데스크톱 GUI 애플리케이션을 개발한 P사
자사 제품을 웹으로 변환하려던 P사. 이러한 과정에서 P사는 이른 결정을 하게 되었고, 결국 엄청난 개발 비용으로 인한 비극을 맞는다.
회사 차량을 관리해주는 W사
W사는 제품에 사용되는 도구들을 너무 일찍 결정하여 적용하는 바람에 모든 서비스 사이의 결합이 안좋아지는 결과를 맞이하게 된다.
관련이 있는 것과 없는 것 사이에 선을 긋는다.
일반적으로 GUI, 업무 규칙, 데이터베이스 간 경계를 만들 수 있다.
데이터베이스와 업무규칙의 경우 위처럼 경계를 구분할 수 있다.
이를 컴포넌트로 표현하면 위와 같다.
Business Rule은 고수준 컴포넌트이다. Database는 세부사항이며 저수준 컴포넌트이다.
Business Rule 컴포넌트에 Database 인터페이스를 두어 의존을 역전시켜 저수준 컴포넌트가 고수준 컴포넌트를 의존하도록 한다.
이로인해, Business Rule은 Database에 대해 알지 못하고, Database가 변경되더라도 영향을 받지 않는다.
UI에도 데이터베이스와 같은 방식을 적용하면, 위와 같이 플러그인 아키텍처를 갖게 된다.
선택적이거나 다양한 형태로 구현될 수 있는 나머지 컴포넌트(UI, Database)로부터 핵심적인 업무 규칙을 분리하고, 독립적으로 존재하도록 만든다.
[정리]
소프트웨어 아키텍처에서 경계선을 그리려면 먼저 시스템을 컴포넌트 단위로 분할해야 한다.
일부 컴포넌트는 핵심 업무 규칙에 해당한다.
나머지 컴포넌트는 플러그인으로, 핵심 업무와는 직접적인 관련이 없지만 필수 기능을 포함한다.
그런 다음 컴포넌트 사이의 화살표가 특정 방향, 즉 핵심 업무를 향하도록 이들 컴포넌트의 소스를 배치한다.
의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배치된다.
시스템 아키텍처는 컴포넌트를 분리하는 경계에 의해서 정의된다. 그리고 이러한 경계는 다양한 형태로 존재한다.
모노리틱 구조 (단일체)
아키텍처 경계 중 가장 단순하고 흔한 형태이며, 단일 실행 파일
함수와 데이터가 단일 프로세서에서 같은 주소 공간을 공유하며 그저 나름의 규칙에 따라 분리되어 있을 뿐이다. (소스 수준 분리 모드)
ex) 정적으로 링크된 C/C++ 프로젝트, 실행 가능한 jar 파일로 묶인 일련의 자바 클래스 파일, 단일 .EXE파일로 묶인 일련의 .NET 바이너리 등
모노리틱 구조의 실행 파일이라도 규칙적인 방식으로 구조를 분리하면 프로젝트를 개발, 테스트, 배포하는 작업에 큰 도움이 된다.
배포형 컴포넌트
아키텍처의 경계가 물리적으로 드러나면서 가장 단순한 형태는 DLL
함수 호출을 통해 컴포넌트 간 통신이 이루어지기 때문에 통신에 드는 비용이 낮다.
ex) DLL, 스레드, 로컬 프로세스, 서비스
스레드
실행 계획과 순서를 체계화하는 방법 (아키텍처 경계 X, 배포 단위 X)모든 스레드가 단 하나의 컴포넌트에 포함될 수도 있고, 많은 컴포넌트에 걸쳐 분산될 수도 있다.
로컬 프로세스
강한 물리적 형태를 띠는 아키텍처 경계로 명령문 또는 시스템 콜에 의해 생성된다.
로컬 프로세스는 독립적인 주소 공간에서 실행되고 프로세스 간 통신은 소켓, 메일박스, 메시지 큐를 이용한다.
각 로컬 프로세스는 정적으로 링크된 단일체이거나 동적으로 링크된 컴포넌트의 조합일 수 있다.
로컬 프로세스 간 분리 전략 : 저수준 → 고수준 프로세스 으로 의존하게 만들고 저수준 프로세스가 플러그인 될 수 있도록 만드는 것이다.
[정리]
단일체를 제외한 대다수의 시스템은 한 가지 이상의 경계 전략을 사용한다.
그리고 저수준의 것이 고수준에 플러그인 된다.
소프트웨어 시스템은 정책을 기술한 것이다.
프로그램은 입력이 출력으로 어떻게 변환되는지 그 과정을 정책으로 기술한 것이다.
소프트웨어 아키텍처는 이 정책을 분리하는 역할을 맡는다.
동일한 시점과 이유로 변경되는 정책은 같은 수준, 같은 컴포넌트에 위치해야 한다.
반대로 다른 시점, 이유로 변경되는 정책은 다른 수준, 다른 컴포넌트로 분리되어야 한다.
각 컴포넌트는 서로를 의존하며 소스 코드, 컴파일 타임의 의존성으로 연결된다.
좋은 아키텍처는 이러한 의존성을 컴포넌트 수준에서, 저수준 컴포넌트가 고수준 컴포넌트를 의존하도록 한다.
수준은 '입력과 출력까지의 거리'를 의미한다.
입력과 출력까지의 거리와 가까울수록 저수준이며, 멀수록 고수준 정책에 해당된다.
‘데이터를 읽어들여 정책에 따라 변환해 출력하는 시스템’ 의 아키텍처
I/O 관련 인터페이스와 Rules 클래스를 하나의 경계로 묶어 경계 밖의 I/O 클래스가 의존하도록 한다.
경계 안의 클래스: 고수준의 요소
경계 밖의 입출력 클래스: 저수준 요소
저수준 정책의 변경(예, FileRead/Write -> SocketRead/Write)은 고수준 정책 관련 클래스에 어떤 영향도 끼치지 않는다.
[ 정책을 컴포넌트로 묶는 기준 ]
정책이 변경되는 이유와 시점에 따른다.
고수준 정책 : 덜 빈번하게, 더 중요한 이유로 변경
저수준 정책 : 더 빈번하게, 긴급성 및 덜 중요한 이유로 변경
사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차
핵심 업무 규칙(Critical Business Rule) : 업무 규칙 중 사업 자체에 핵심적이며, 자동화된 시스템이 없더라도 존재하는 업무 규칙
핵심 업무 데이터(Critical Business Rule) : 핵심 업무 규칙에서 요구하는 데이터
이 둘은 엔티티(Entity) 객체로 묶을 수 있다.
엔티티
: 시스템 내부의 객체
핵심 업무 데이터를 기반으로 일련의 핵심 업무 규칙을 구체화한다.
엔티티는 어떤 시스템으로 구성할지, 어떻게 저장할지 등의 방식과는 무관하다.
엔티티 객체: 핵심 업무 데이터
엔티티의 인터페이스: 핵심 업무 규칙의 구현한 함수
유스케이스
: 입력으로부터 출력까지의 단계까지 자동화된 시스템이 사용되는 규칙 (애플리케이션에 특화된 규칙)
엔티티 내부의 핵심 업무 규칙을 언제 어떻게 호출할 지를 명시하고 엔티티와 사용자 간 상호 작용을 규정
유스케이스는 시스템의 입출력과 가까이 있으므로 저수준 개념, 엔티티 객체는 시스템의 인출력과 멀리 있으므로 고수준 개념이다.
따라서 엔티티는 유스케이스에 대해서 알지 못하며, 유스케이스는 엔티티를 의존하게 된다.