일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 커링
- 가장 큰 수
- 코딩 테스트
- @EnableScheduling
- 정렬
- 검색 기능 확장
- 다리를 지나는 트럭
- @Getter
- Java
- 스프링 스케쥴러
- 쿠버네티스
- 크론 표현식
- 해시
- 루씬 인 액션
- H-index
- @Data
- 완주하지 못한 선수
- 기능개발
- 스택/큐
- 모던 자바 인 액션
- 프로그래머스
- @Setter
- @configuration
- 롬복 어노테이션
- 알고리즘
- 영속 자료구조
- kubenetes
- 전화번호 목록
- 고차원 함수
- K번째수
- Today
- Total
Today I Learned
05 아키텍처 (4) 본문
18장. 경계 해부학
경계 횡단하기
소스코드 모듈 하나가 변경되면, 이에 의존하는 모든 소스 코드 모듈도 변경하거나 다시 컴파일해서 재배포해야할지도 모른다. 따라서 적절한 위치에서 경계를 횡단하게 하는 비결은 소스코드 의존성 관리에 있다.
경계는 이러한 변경이 전파되는 것을 막는 방화벽을 구축하고 관리하는 수단으로써 존재한다.
두려운 단일체
아키텍처 경계 중 가장 단순하고 흔한 형태는 물리적으로 엄격하게 구분되지 않은 형태이다.
함수와 데이터가 단일 프로세서에서 같은 주소 공간을 공유하며 그저 나름의 규칙에 따라 분리되어 있다.
가장 단순한 형태의 경계 횡단은 저수준 클라이언트에서 고수준 서비스로 향하는 함수 호출이다.
런타임 의존성과 컴파일타임 의존성이 모두 저수준에서 고수준 컴포넌트로 향한다.
만약 고수준 클라이언트가 저수준 서비스를 호출해야 한다면 동적 다형성을 사용하여 의존성을 역전시킬 수 있다.
이렇게하면 제어흐름은 왼쪽에서 오른쪽으로 경계를 횡단하지만, 의존성은 오른쪽에서 왼쪽으로 향한다.
대체로 이러한 시스템에서 컴포넌트는 소스 코드 형태로 전달되는 소스 수준 분리모드에 해당한다.
배포형 컴포넌트
.NET DLL, 자바 jar 파일, 루비 젬, 유닉스 공유 라이브러리 등의 동적 라이브러리는 아키텍처의 경계가 물리적으로 드러난 형태이다.
이 형태의 컴포넌트는 따로 컴파일하지 않아도 바로 사용 가능하며, 바이너리와 같이 배포 가능한 형태로 전달되는 배포 수준 결합 분리 모드에 해당한다.
배포 과정에서만 차이가 날 뿐, 배포 수준의 컴포넌트는 단일체와 동일하다.
스레드
스레드는 아키텍처 경계도 아니며 배포 단위도 아니다. 스레드는 실행 계획과 순서를 체계화하는 방법에 가깝다.
모든 스레드가 단 하나의 컴포넌트에 포함될 수도 있고, 많은 컴포넌트에 걸쳐 분산될 수도 있다.
로컬 프로세스
로컬 프로세스는 훨씬 강한 물리적 형태를 띠는 아키텍처 경계이다.
동일한 프로세서 또는 하나의 멀티코어 시스템에 속한 여러 프로세서들에서 실행되지만, 각각 독립된 주소 공간에서 실행된다.
로컬 프로세스는 일종의 최상위 컴포넌트로, 컴포넌트 간 의존성을 동적 다형성을 통해 관리하는 저수준 컴포넌트로 구성된다.
로컬 프로세스 간 분리 전략은 단일체나 바이너리 컴포넌트의 경우와 동일하게 항상 고수준 컴포넌트를 향한다.
로컬 프로세스 경계를 지나는 통신(운영체제 호출, 마샬링/언마샬링, 프로세스 간 문맥 교환 등)은 비싼 작업이므로 빈번하게 이뤄지지 않도록 제한해야 한다.
서비스
서비스는 물리적인 형태를 띠는 가장 강력한 서비스 경계이다.
서비스는 물리적 위치에 구애받지 않으며, 서비스들은 모든 통신이 네트워크를 통해 이뤄진다고 가정한다.
서비스 경계를 지나는 통신은 매우 느리므로, 지연(latency)에 따른 문제를 고수준에서 처리할 수 있어야 한다.
로컬 프로세스에 적용한 규칙과 동일하게 저수준 서비스는 반드시 고수준 서비스에 '플러그인' 되어야 한다.
19장. 정책과 수준
소트프웨어 시스템이란 정책을 기술한 것이다.
수준
수준(level)은 '입력과 출력까지의 거리'이다. 입력과 출력으로부터 멀어질수록 정책의 수준은 높아진다.
입력과 출력을 다루는 정책은 시스템에서 최하위 수준에 위치한다.
번역 컴포넌트는 입력과 출력으로부터 가장 멀리 떨어져있는 최고 수준의 컴포넌트이다.
주목할 점은 데이터 흐름과 소스 코드 의존성이 항상 같은 방향을 가리키지 않는다는 사실이다.
function encrypt() {
while(true)
writeChar(translate(readChar()));
}
위 코드는 고수준의 encrypt 함수가 저수준인 readChar와 writeChar 함수에 의존하는 잘못된 아키텍처이다.
입력과 출력에 변화가 생기더라도 암호화 정책에 영향이 없도록, 고수준의 암호화 정책은 저수준의 입력/출력 정책으로부터 분리시켜야 한다.
이처럼 모든 소스코드 의존성의 방향이 고수준 정책을 향할 수 있도록 정책을 분리했다면 변경의 영향도를 줄일 수 있다. 시스템의 최저 수준에서 중요하지 않지만 긴급한 변경이 발생하더라도, 높은 위치의 중요한 수준에 미치는 영향은 거의 없게 된다.
20장. 업무 규칙
업무규칙은 사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차다. 컴퓨터로 구현했는지는 상관없다.
예를 들어 대출에 N%의 이자를 부과한다는 사실은 은행이 돈을 버는 업무 규칙이다. 컴퓨터 프로그램으로 이자를 계산하는, 직원이 직접 계산하든 관계가 없다.
이러한 규칙을 핵심 업무 규칙이라고 부르며, 이러한 규칙에 필요한 데이터(대출 잔액, 이자율, 지급 일정 등)를 핵심 업무 데이터라고 부른다.
엔티티
엔티티는 핵심 업무 데이터를 기반으로 동작하는 일련의 조금한 핵심 업무 규칙을 구체화한다.
엔티티의 인터페이스는 핵심 업무 데이터를 기반으로 동작하는 핵심 업무 규칙을 구현한 함수들로 구성된다.
대출을 뜻하는 Loan 엔티티를 UML 클래스로 나타내면 다음과 같다.
세 가지의 핵심 업무 데이터를 포함하며, 데이터와 관련된 세 가지 핵심 업무 규칙을 인터페이스로 제공한다.
이 클래스는 어떤 시스템에서도 업무를 수행할 수 있으며, 시스템의 표현 형식이나 데이터 저장 방식, 해당 시스템에서 컴퓨터가 배치되는 방식 등과 무관하다.
엔티티의 유일한 요구조건은 핵심 업무 데이터와 핵심 업무 규칙을 하나로 묶어 별도의 소프트웨어 모듈로 만들어야한다는 것이다.
유스케이스
모든 업무규칙이 엔티티처럼 순수한 것은 아니다. 자동화된 시스템이 동작하는 방법을 정의하고 제약함으로써 수익을 얻거나 비용을 줄이는 업무 규칙도 존재한다.
유스케이스는 자동화된 시스템이 사용되는 방법을 설명한다. 사용자가 제공해야 하는 입력, 보여줄 출력, 해당 출력을 생성하기 위한 처리 단계를 기술한다.
유스케이스는 시스템이 사용자에게 어떻게 보이는지 설명하지 않는다. 이보다는 애플리케이션에 특화된 규칙을 설명하며, 이를 통해 사용자와 엔티티 사이의 상호작용을 규정한다.
엔티티는 고수준의 개념이며, 유스케이스는 저수준의 개념이다. 유스케이스는 단일 애플리케이션에 특화되어 있으므로, 해당 시스템의 입력과 출력에 보다 가깝게 위치하기 때문이다.
요청 및 응답 모델
유스케이스는 단순한 요청 데이터 구조를 입력으로 받아들이고, 출력으로 반환한다. 이 데이터 구조는 어떤 것에도 의존하지 않는다.
요청 및 응답 모델과 엔티티는 독립적이어야 한다. 시간이 지나면서 두 객체는 다른 이유로 변경될 것이고, 두 객체를 어떤 식으로든 함께 묶는 행위는 공통 폐쇄 원칙과 단일 책임 원칙을 위배하게 된다.
'클린 아키텍처' 카테고리의 다른 글
05 아키텍처 (6) (0) | 2021.09.03 |
---|---|
05 아키텍처 (5) (0) | 2021.08.27 |
05 아키텍처 (3) (0) | 2021.08.20 |
05 아키텍처 (2) (0) | 2021.08.12 |
05 아키텍처 (1) (0) | 2021.08.06 |