소프트웨어 아키텍처 101 - 마크 리처즈, 닐 포드
1 서론
소프트웨어 아키텍트의 길은 왜 분명하지 않을까?
- 직업 자체에 대한 명확한 정의가 없다
- 업무 범위가 방대하다
- 과거 모듈성, 컴포넌트, 패턴 등 순수 기술적인 부분 + MSA 등 계속해서 확대
- 소프트웨어 개발 생태계 발전에 따라 끊임없이 변화한다
- 관련 자료들은 역사적 연관성을 강조한다
따라서 아키텍처를 공부하며 명심해야 할 것은, 현재의 환경에 맞는 결정을 하는 것이다.
소프트웨어 아키텍처는 시스템의 청사진이다. 시스템 아키텍처를 이해하는 방법 중 하나는 아래와 같다.
- 아키텍처 특성
- 아키텍처 결정
- 반드시 지켜야 할 원칙
- 시스템 제약 조건을 형성
- 설계 원칙
- 가이드라인
- 시스템 구조
- 마이크로서비스, 레이어드, 마이크로커널 등
제1법칙 소프트웨어 아키텍처의 모든 것은 다 trade-off이다.
제2법칙 ‘어떻게’보다 ‘왜’에 더 주목해야 한다.
2 아키텍처 사고
아키텍처 사고란 아키텍처와 설계의 차이를 알고 둘을 통합한 솔루션을 모색하는 것.
- 아키텍처: 비즈니스 요구사항 분석→아키텍처 특성, 스타일, 컴포넌트 구조 도출
- 설계 : 아티팩트 → 컴포넌트의 클래스 설계, UI, 소스 코드
전통적인 역할 모델은 아키텍처→ 설계 단방향 이었으나, 제대로 된 아키텍처를 만들려면 소통의 단절을 부수고, 협력하여 프로젝트 생명 주기의 일부로 동기화 되어야 한다.
트레이드오프 분석
입찰 프로듀서 서비스가 입찰을 생성하고 그 금액을 입찰 캡처, 추적, 분석 서비스에 전달하는 경매 시스템을 설계한다고 가정한다.
토픽을 이용한 서비스 간 통신을 이용할 경우
- 장점
- 아키텍처 확장성(extensibility)
- 입찰 이력을 조회하는 새로운 서비스를 도입하더라도 변경할 필요가 없는 구조이다.
- 서비스 디커플링
- 입찰 프로듀서 서비스는 입찰 정보를 어느 서비스가 어떻게 사용하는지 모른다
- 아키텍처 확장성(extensibility)
- 단점
- 데이터 액세스, 보안 문제
- 누구나 입찰 데이터에 액세스할 수 있으며 도청이 쉽다.
- 서로 다른 계약 지원 불가
- 토픽을 수신한 모든 서비스는 동일한 계약 및 데이터 세트를 받아야 한다.
- 모니터링과 프로그래밍 방식의 확장성 떨어짐
- 토픽 메시지 개수를 모니터링할 수 없고, auto-scaling 기능이 지원되지 않음
- 데이터 액세스, 보안 문제
큐를 이용한 서비스 간 통신을 이용할 경우
- 장점
- 데이터 액세스, 보안 문제
- 큐를 수신하는 지정된 컨슈머만 접근 가능하다.
- 개별 계약 지원
- 토픽을 수신한 모든 서비스는 동일한 계약 및 데이터 세트를 받아야 한다.
- 모니터링과 프로그래밍 방식의 확장성
- 각 큐를 개별 모니터링 + 컨슈머 개별 로드 밸런싱 가능
- 데이터 액세스, 보안 문제
- 단점
- 아키텍처 확장성 떨어짐
- 입찰 이력을 조회하는 새로운 서비스를 도입한다면, 서비스용 큐가 새로 필요하고, 프로듀서 서비스에 대한 변경이 불가피하다.
- 서비스 커플링
- 입찰 프로듀서 서비스는 입찰 정보가 어느 서비스에 어떻게 사용하는지 정확히 안다.
- 아키텍처 확장성 떨어짐
소프트웨어 아키텍처는 이런 트레이드오프를 분석하고 비즈니스 동인, 환경 등의 팩터에 따라 더 나은 방법을 택한다. (It depends!)
아키텍트가 코딩 실무 능력 유지하는 방법
- POC(proof-of-concept)를 자주 해본다
- 각 제품을 응용한 예제 코드를 작성하고 실행 결과를 비교하여 공수, 솔루션의 확장성, 성능, 내고장성등의 아키텍처 특성을 비교한다.
- 가능한 프로덕션 수준의 좋은 코드를 작성하여 레퍼런스 아키텍처나 다른 사람들이 참고할만한 샘플 코드로 만들자.
- 기술 부채 스토리나 아키텍처 스토리에 전념한다
- 버그를 잡는다
- 개발팀 프로세스 자동화 툴을 만든다
- 피트니스 함수를 사용해 아키텍처 컴플라이언스를 자동화한다
- 코드리뷰를 한다
3 모듈성
모든 플랫폼은 연관된 코드를 모듈로 묶는 방법을 지원한다. 또한 아키텍트가 분석해야할 메트릭, 피트니스 함수, 시각화 등 많은 도구가 모듈성에 기반한다.
모듈성은 일종의 구성 원리(organizing principle)로 증가하는 소프트웨어 시스템 엔트로피를 모델링하고, 질서를 유지한다.
모듈은 복잡한 구조를 만드는 데 쓰이는 각각의 표준화된 부품이나 독립적인 단위이다.
아키텍트는 개발자각 코드를 어떻게 패키징하는지 알아야 한다. 여러 패키지가 단단히 커플링되어있으면 그 중 하나를 다른 작업에 사용하기 어려워진다.
모듈성 측정
응집
응집(cohesion)은 모듈을 구성하는 구성요소가 서로 얼마나 연관되어 있는가를 나타낸다. 응집은 커플링보다 덜 정확한 메트릭으로 아키텍처 재량에 따라 다르게 측정될 수 있다.
응집도의 측정 범위, 좋은 순서대로
- 기능적 응집(functional cohesion)
- 모듈의 각 파트는 나머지 파트와 연결되어 있고 기능상 필요한 모든 것이 모듈에 존재한다.
- 순차적 응집(sequential cohesion)
- 두 모듈이, 한쪽에 데이터를 출력하면 다른 쪽이 그것을 입력 받는 형태로 상호작용한다.
- 소통적 응집(communication cohesion)
- 두 모듈이, 각자 정보에 따라 작동하거나 어떤 출력을 내는 형태로 통신 체인을 형성한다.
- 절차적 응집(procedural cohesion)
- 두 모듈은 정해진 순서대로 실행된다.
- 일시적 응집(temporal cohesion)
- 모듈이 시점 의존성(timing dependency)에 따라 연관된다.
- 논리적 응집(logical cohesion)
- 기능이 아닌 논리적으로 응집되어있다. 자바 프로젝트의 StringUtils 패키지가 좋은 예.
- 동시적 응집(coincidental cohesion)
- 같은 소스 파일에 모듈 구성요소가 들어가있지만 아무런 연관성이 없다.
LCOM(Lack of Cohesion in Methods)
카이댐버와 케메러의 매서드의 응집 결여도이다. 공유 필드를 통해 공유되지 않는 메서드의 총 개수를 뜻한다.
\[LCOM96b = \frac{1}{a}\sum_{k=0}^{a} \frac{m-\mu(Aj)}{m}\]커플링
추상도(abstractness)는 추상 클래스, 인터페이스 등의 추상 아티팩트(abstract artifact)와 구상 아티팩트(concrete artifact, 구현체)의 비율, 즉 구현 대비 추상화를 나타낸다.
\[A(추상도) = \frac{\sum m^a}{\sum m^c}\]불안정도(instanbility)는 코드베이스의 변동성을 의미한다. 원심 커플링과 (구심 커플링 + 원심 커플링)의 비율로 계산한다.
구심커플링은 컴포넌트, 클래스, 함수 등의 코드 아티팩트로 유입되는 접속 수를 의미한다.
원심 커플링은 다른 코드 아티팩트로 유출되는 접속 수를 나타낸다.
\[I(불안정도)=\frac{C^e}{C^e+C^a}\]메인 시퀀스로부터의 거리(distance from the main sequence)
추상도와 불안정도를 이용하여 이상적인 관계를 계산한다.
\[D = |A+I-1|\]- 그래프가 오른쪽 위로 치우칠 경우
- 쓸모없는 구역(zone of useless)
- 추상화를 너무 많이해서 사용하기 어려운 코드
- 그래프가 왼쪽 아래로 치우칠 경우
- 고통스러운 구역(zone of pain)
- 추상화를 거의 안하고 구현만 잔뜩 넣어 취약하고 관리하기 어려운 코드
커네이선스
두 컴포넌트 중 한쪽이 변경될 경우 다른 쪽도 변경해야 전체 시스템 정합성이 맞는다면 커네이선스를 갖는다.
커네이선스는 정적 커네이선스와 동적 커네이선스로 분류할 수 있다.
- 정적 커네이선스(static connascence)
- 소스 코드 레벨의 커플링으로 구심/원심 커플링을 발전시킨 개념
- 명칭 커네이선스(CoN)
- 여러 컴포넌트의 엔티티명이 일치해야 한다.
- 타입 커네이선스(CoT)
- 여러 컴포넌트의 엔티티 타입이 일치해야 한다.
- 의미 커네이선스(CoM) 또는 관례 커네이선스(CoC)
- 여러 컴포넌트에 걸쳐 어떤 값의 의미가 일치해야 한다.
- 예) 상수 대신 숫자를 하드코딩하는 경우
- 위치 커네이선스(CoP)
- 여러 컴포넌트는 값의 순서가 일치해야 한다.
- 예) 매개변수
- 알고리즘 커네이선스(CoA)
- 여러 컴포넌트는 특정 알고리즘이 일치해야 한다.
- 예) 보안 해시 알고리즘
- 동적 커네이선스(dynamic connascence)
- 런타임 호출을 분석하는 커네이선스
- 실행 커네이선스(CoE)
- 여러 컴포넌트의 실행 순서가 중요하다.
- 시점 커네이선스(CoT)
- 여러 컴포넌트의 실행 시점이 중요하다.
- 예) 동시에 실행 중인 두 스레드 때문에 경합 조건이 발생하여 공동 작업의 결과에 영향을 끼치는 경우
- 값 커네이선스(CoV)
- 상호 연관된 다수의 값들을 함께 변경한다.
- 예) 트랜잭션
- 식별 커네이선스(Col)
- 여러 컴포넌트가 동일한 엔티티를 참조한다.
- 예) 독립적인 두 컴포넌트가 분산 큐같은 자료구조를 공유해서 업데이트 하는 경우
커네이선스 속성은 다음과 같이 분석할 수 있다.
- 강도
- 아키텍트와 개발자는 더 나은 유형의 커네이선스를 리팩토링해서 코트베이스의 커플링 특성을 개선할 수 있다.
- 정적 커네이선스는 소스 코드 분석 또는 최신 도구를 이용해 쉽게 개선할 수 있다.
- 지역성
- 코드베이스의 모듈이 서로 얼마나 가까이 있는가
- 근접한 코드는 일반적으로 분리된 코드보다 높은 형태의 커네이선스를 가진다.
- 정도
- 커네이선스가 미치는 영향의 규모
- 소수의 클래스에 영향을 미치는가, 수많은 클래스에 영향을 미치는가
- 강한 정도의 커네이선스를 약한 정도의 커네이선스로 전환하는 것이 좋다.
- 커네이선스가 미치는 영향의 규모
1990년대 커네이선스의 문제점
- 아키텍트가 관심 있어하는 아키텍처의 구조보다 저수준 코드의 세부분을 관찰한다.
- 아키텍트가 내려야할 근본적인 결정에 관한 문제는 다루지 않는다.
4 아키텍처 특성 정의
아키텍트는 개발팀과 함께 도메인, 비즈니스 요구사항을 정의할 수 있지만, 주로 소프트웨어로 처리할 일 중 도메인 기능과 직접적인 관련이 없는 모든 것들, 즉 아키텍처 특성을 정의, 발견, 분석하는 일을 수행한다.
비기능 요구사항, 품질 속성이라는 말도 많이 사용하지만 부정적인 인상을 주는 단어로 아키텍처 특성이라는 용어를 선호한다.
아키텍처 특성의 기준은 다음과 같다.
- 비도메인 설계 고려 사항을 명시한다.
- 애플리케이션으로 처리할 일은 구체적인 요구사항으로 정리한다.
- 요구사항을 구현 하는 방법, 선택을 하게 된 이유와 관련된 운영/설계 기준을 명시한다.
- 설계의 구조적 측면에 영향을 미친다.
- 이 아키텍처 특성은 어떤 특별한 구조적 요소를 고려해야 하는가?
- 애플리케이션 성공에 절대적으로 중요하다.
- 아키텍처 특성에 따른 설계 복잡도 가중을 고려하여 가급적 아키텍처 특성을 작게 선정하는 일도 중요하다.
- 아키텍처의 명시적 특성과 암묵적 특성
- 명시적 특성
- 요구사항 정의서나 다른 지침서에 개시된 특성
- 암묵적 특성
- 요구사항 정의서에 없지만 프로젝트 성공을 위해 필요한 특성
- 가용성, 신뢰성, 보안 등 애플리케이션의 근간
- 명시적 특성
아키텍처 특성 목록
운영 아키텍처 특성
- 가용성(availability)
- 시스템이 얼마나 오랫동안 사용 가능해야 하는가
- 연속성(continuaility)
- 재해 복구 능력
- 성능(performance)
- 스트레스 테스트, 피크 분석, 기능의 사용 빈도 분석, 필요 용량, 응답 시간 등
- 복구성(recoverability)
- 비즈니스 연속성 요구사항, 백업 전략과 하드웨어 다중화 요건에 영향을 미친다.
- 신뢰성/안전(reliability/safety)
- 시스템에 페일 세이프가 필요한가? 시스템 실패 시 회사에 거액 손실이 발생하는가?
- 견고성(robustness)
- 프로그램 실행 중 인터넷 접속 끊김, 정전, 하드웨어 실패 등 에러 및 경계 조건을 감당하는 능력
- 확장성(scalability)
- 유저 수, 요청 수가 늘어나도 시스템이 그에 맞는 성능을 발휘하는 능력
구조 아키텍처 특성
- 설정성(configurability)
- 최종 유저가 편한 인터페이스를 통해 소프트웨어 설정을 쉽게 바꿀 수 있는가?
- 신장성(extensibility)
- 새로운 기능을 삽입하는 일의 중요성
- 설치성(installability)
- 필요한 모든 플랫폼에 쉽게 시스템을 설치할 수 있는가?
- 활용성/재사용(leverageability/reuse)
- 공통 컴포넌트를 여러 제품에 활용할 수 있나?
- 지역성(locality)
- 데이터 입력/조회하는 화면에서 다국어가 지원되는가?
- 유지보수성(maintainability)
- 시스템을 얼마나 쉽게 변경/개선할 수 있나?
- 이식성(portability)
- 하나 이상의 플랫폼에서 시스템을 실행할 수 있나?
- 지원성(supportability)
- 애플리케이션은 어느 정도의 기술 지원을 필요로 하나? 시스템에서 발생한 에러를 디버깅하려면 로깅 및 기타 기능이 어느 수준으로 뒷받침되어야 하는가?
- 업그레이드성(upgradeability)
- 이 애플리케이션/솔루션의 구 버전을 새 버전으로 쉽고 빠르게 업그레이드할 수 있는가?
아키텍처 공통 특성
- 접근성(accessibility)
- 색맹, 청각 장애인 등 모든 유저가 접근하는 데 불편함이 없는가?
- 보관성(archivability)
- 데이터를 따로 아카이빙해야 하나, 아니면 일정 시간 경과 후 삭제해야 하나?
- 인증(authentication)
- 유저가 본인이 맞다는 것을 증명하기 위해 필요한 보안 요구사항
- 인가(authorization)
- 유저가 애플리케이션에서 정해진 기능만 사용할 수 있도록 강제하는 보안 요구사항
- 합법성(legal)
- 시스템 운영상 법적 제약조건이 있는가?
- 프라이버시(privacy)
- 회사 내부 임직원의 트랜잭션을 외부에 드러내지 않는 기능
- 보안(security)
- 데이터를 암호화한 후 데이터베이스에 보관해야 하나? 내부 시스템 간 네트워크 통신도 암호화해야 하나? 원격 유저 액세스는 어떤 종류의 인증이 필요한가?
- 사용성/성취성(usability/achieveability)
- 유저가 애플리케이션/솔루션을 이용하여 원하는 목적을 달성하기 위해 필요한 교육/훈련 수준
최고의 아키텍처를 고집하지 말고 나쁜 것 중에서 제일 나은 아키텍처를 선택하라
아키텍처가 내린 결정은 상충되는 여러 문제들이 뒤얽힌 트레이드오프로 귀결되는 경우가 많다.
애자일 소프트웨어의 교훈과 같이, 가능한 한 아키텍처 설계를 꾸준히 반복해보는 것이 좋다.
5 아키텍처 특성 식별
아키텍처는 세 가지 출처 즉 도메인 관심사, 요구사항, 암묵적 도메인 지식에서 아키텍처 특성을 밝혀낸다.
도메인 관심사에서 아키텍처 특성 도출
도메인 이해관계자와 협력하여 주요 아키텍처 특성을 정의할 때 최종 목록을 가능한 한 짧게 하는 것이 좋다.
너무 많은 아키텍처 특성을 수용하여 제네릭 아키텍처를 설계하려 하는 것은 안티패턴이다. 아키텍트와 개발자가 당초 의도했던 문제 영역의 해결을 시도하기도 전에 아키텍처가 너무 복잡해져버린다.
주요 담당자들에게 가장 중요한 아키텍처 특성 3개를 선택하라고 한 다음 합의를 이끌어내는 것도 좋은 방법이다. 가장 중요한 것이 무엇일까 논의하고, 아키텍트가 중요한 결정을 내리기 전에 트레이드오프를 분석하는 데에도 도움이 된다.
대부분의 아키텍처 특성은 핵심 도메인 이해관계자들의 의견을 듣고 도메인 관점에서 무엇이 중요한지 의견을 교환하면서 정리한다. 아키텍트와 도메인 이해관계자들이 다른 언어로 소통하는 문제를 막기 위해 도메인 관심사를 아키텍처 특성으로 옮기는 작업이 필요하다.
요구사항에서 아키텍처 특성 도출
요구사항 정의서에 명시된 문장에서 도출되는 아키텍처 특성도 있다. 아키텍트가 알고 있는 도메인 지식에서 도출되는 특성들이 있기 때문에 아키텍트가 도메인 지식을 갖고 있으면 이롭다.
명시적 특성과 암묵적 특성
- 명시적 특성
- 필요한 설계의 일부로서 요구사항 정의서에 기술된다.
- 암묵적 특성
- 요구사항 정의서에 따로 없는 아키텍처 특성도 있지만 각각 중요한 설계 요소가 된다.
- 예) 가용성, 신뢰성, 보안이 있을 수 있고 중요도에 따라 우선순위가 달라질 수 있다.
6 아키텍처 특성의 측정 및 거버넌스
아키텍처 특성 측정
아키텍처 특성은 모호할 수 있어 객관적으로 정의할 필요가 있다.
- 물리학이 아니다
- 동일한 용어도 업계에서 바라보는 시각이 다르다.
- 법적인 상황이나 우발적으로 의미가 정해지는 경우도 있다.
- 정의가 너무 다양하다
- 개발자, 아키텍트, 운영자 모두 정의를 통일할 필요가 있다.
- 너무 복합적이다.
- 바람직한 아키텍처 특성은 대부분 더 작은 여러 특성들로 구성된다.
복합적인 특성을 더 잘게 나누면 다음과 같다.
- 운영적 특성
- 팀 목표에 따라 달성 가능한 수치를 목표로 삼는다.
- 예를 들어 확장성을 모니터링하는 비디오 스트리밍 서비스 업체라면 시간에 따라 어떤 추이를 보이는지 측정하고 통계 모델을 수립한다.
- 구조적 특성
- 내부 구조에 관한 특성은 목표치가 확실하지 않다. 이러한 경우 다른 메트릭과 공통 도구를 이용해서 코드 구조에 관한 부분을 볼 수 있다.
- 예를 들어 코드의 복잡도는 순환 복잡도(CC, cyclomatic complexity)라는 메트릭을 통해 측정할 수 있다.
- CC는 함수/메서드, 클래스, 또는 애플리케이션에서 코드 복잡도를 객관적으로 나타내는 지표
- CC = E-N+2P (P는 연결된 컴포넌트 수)
- 업계 기준은 10 이하를 권장하나, 5 이하로 나와야 괜찬은 짜임새있는 코드
- 프로세스 측정
- 소프트웨어 개발 프로세스와 교차하는 아키텍처 특성도 있다.
- 민첩성은 시험성, 배포성 등의 특성으로 나눌 수 있는 복합적인 아키텍처 특성이다.
거버넌스와 피트니스 함수
아키텍처 거버넌스란 아키텍트가 영향력을 행사하려는 모든 소프트웨어 개발 프로세스를 포괄한다.
익스트림 프로그래밍에서 비롯된 소프트웨어 프로젝트의 자동화 움직임은 지속적 통합으로 발전하고, 운영도 자동화하는 데브옵스 체계에 이르렀으며 좋은 솔루션이 많이 등장하고 있다.
아키텍처 피트니스 함수
아키텍처 거버넌스의 여러 부문을 자동화하기 위해 피트니스 함수를 사용한다. 피트니스 함수는 결과가 목표에 얼마나 근접했는지를 나타내는 목표 함수이다.
아키텍처 피트니스 함수는 어떤 아키텍처 특성(또는 그런 특성들의 조합)의 객관적인 무결성을 평가하는 모든 매커니즘이다.
아키텍처 특성에 따라 피트니스 함수를 다양한 도구로 구현할 수 있다.
모듈성의 다양한 측면을 태스트하는 피트니스 함수를 소개한다.
순환 의존성
순환 의존성이 형성되면 개발자가 어느 한 컴포넌트를 재사용하기 위해 그에 딸린 다른 컴포넌트들도 함께 가져와야 하므로 모듈성이 매우 떨어진다. 또 컴포넌트 간에 커플링이 증카할 수록 아키텍트는 안티패턴에서 헤어나오지 못한다. 아키텍트는 JDepend라는 메트릭 도구로 패키지 간 의존성을 체크할 수 있다. 이 테스트를 프로젝트의 지속적 빌드의 일부로 장치하여 순환 참조를 방지할 수 있다.
‘메인 시퀀스로부터의 거리’ 피트니스 함수
‘메인 시퀀스로부터의 거리’와 같은 난해한 메트릭도 피트니스 함수를 이용해 확인할 수 있다. JDepend로 수용 가능한 임계치를 설정하고 클래스가 범위를 벗어나면 테스트를 실패 처리한다.
아키텍트는 개발자에게 피트니스 함수 사용을 권하기 전에 정확한 목적을 이해할 수 있도록 설명해야한다.
최근 수년 간 피트니스 함수 도구는 점점 더 정교해졌고 목적에 따라 특화되는 추세이다. JUnit의 영향을 받아 탄생한 ArchUnit은 레이어 간의 올바른 관계를 정의하고 실천하는 검증 피트니스 함수 코드를 제공한다. 넷플릭스의 카오스 멍키(Chaos Monkey), 시미안 아미(Simian Army)도 피트니스 함수의 응용 사례이다.
7 아키텍처 특성 범위
전통적으로는 아키텍처 특성의 범위를 시스템 레벨에 두었으나, 현대적인 공학 기술의 등장과 마이크로서비스 등의 아키텍처 스타일이 가능해지면서 아키텍처 특성의 범위는 좁아졌다.
커플링과 커네이선스
구심/원심 커플링과 같은 코드 레벨의 커플링 메트릭은 아키텍처 분석용으로는 너무 세분도가 높다. (전체 시스템의 구조를 이해하는데 적합하지 않다) 커네이선스는 커플링을 보완하기 위한 메트릭이다.
두 컴포넌트 중 한쪽이 변경될 경우 다른 쪽도 변경해야 전체 시스템의 정합성이 맞는다면 커네이선스를 가지고 있는 것이다.
- 정적 커네이선스
- 정적 코드 분석으로 발견할 수 있다.
- 예) 동일한 클래스를 공유한다.
- 동적 커네이선스
- 런타임 동작과 관련되어있다.
- 예) 서비스간 호출을 하는 경우
- 동기
- 예) 분산 서비스간 동기 호출
- 비동기
- 예) 이벤트 기반 아키텍처의 비동기 호출
아키텍처 퀀텀과 세분도
아키텍처 퀀텀이란 높은 응집도(high functional cohesion)와 동기적 커네이선스(synchronous connascence)를 가진, 독립적으로 배포 가능한(independently deployable) 아티팩트이다.
- 독립적으로 배포 가능
- 아키텍처 퀀텀은 아키텍처의 다른 파트와 독립적으로 작동되는 모든 필수 컴포넌트를 포함한다.
- 높은 기능 응집도
- 응집도는 컴포넌트 설계에 따라 구현된 코드가 얼마나 목적에 맞게 통합되어 있는지를 나타낸다.
- 예를 들어 특정 도메인 컴포넌트는 응집도가 높고, Utility 컴포넌트는 응집도가 낮다.
- 동기적 커네이선스
- 아키텍처 퀀텀을 형성하는 애플리케이션 콘텍스트 내부 또는 분산 서비스간의 동기 호출을 의미한다.
- 두 서비스가 두드러진 차이를 나타내는 경우, 호출부의 확장성이 훨씬 좋을 경우 타임아웃과 신뢰성 문제가 일어날 것이다.
- 아키텍처 퀀텀을 형성하는 애플리케이션 콘텍스트 내부 또는 분산 서비스간의 동기 호출을 의미한다.
8 컴포넌트 기반 사고
컴포넌트란 모듈(연관된 코드의 묶음)을 물리적으로 패키징한 것이다.
컴포넌트 범위
가장 단순한 컴포넌트는 클래스보다 한 단계 높은 수준의 모듈로 코드를 래핑하는 것이고 보통 이를 라이브러리라고 한다. 라이브러리는 대개 호출부 코드와 동일한 메모리 주소에서 실행되며 해당 언어의 함수 호출 매커니즘을 이용해 통신한다. 또한 라이브러리는 일반적으로 컴파일 타임에 의존한다.
컴포넌트는 아키텍처에서 서브시스템이나 레이어 형태로 나타나며 배포 가능한 작업의 단위이다.
서비스는 또다른 종류의 컴포넌트로서 자신의 주소공간에서 실행되며, TCP/IP 같은 저수준 네트워크 프로토콜이나 REST, 메시지 큐 같은 고수준 포맷을 통해 통신한다. 마이크로서비스 아키텍처에서의 서비스는 배포 가능한 독립적인 단위이다.
아키텍트 역할
아키텍트는 아키텍처 내부의 컴포넌트를 정의, 개선, 관리, 통제하는 일을 한다. 소프트웨어 아키텍트는 아키텍처 특성과 소프트웨어 시스템 요구사항을 종합하여 비즈니스 분석가, 분야별 전문가, 개발자, QA 엔지니어, 운영자 엔터프라이즈 아키텍트와 함께 소프트웨어 초기 설계를 한다.
아키텍트는 클래스 설계에 참여해서도 안되고 시스템의 세세한 설계 결정에 관여해서도 안된다.
아키텍처 분할
최상위 아키텍처를 분할하는 두가지 방법
- 기술 분할 - 레이어드 아키텍처
- 시스템 기능을 기술적인 능력, 즉 프레젠테이션, 컨트롤러, 서비스, 퍼시스턴스 등으로 분할
- 관련 코드를 쉽게 찾을 수 있다.
- 콘웨이 법칙의 실현이다
- 시스템을 설계하는 조직은 그 조직의 소통 구조를 그대로 옮겨 놓은 설계도를 그릴 수밖에 없다.
- 기술 관심사의 분리로 유용한 수준의 디커플링을 만든다.
- 장점
- 커스텀 코드가 명확하게 분리된다.
- 레이어드 아키텍처 패턴에 더 가깝게 맞출 수 있다.
- 단점
- 전역 커플링이 높다. 따라서 공통 또는 로컬 컴포넌트 중 하나라도 변경되면 다른 컴포넌트들이 영향을 받을 가능성이 높다.
- 개발자가 공통 레이어, 로컬 레이어 양쪽에 도메인을 복제해야 할 수도 있다.
- 데이터 레벨의 커플링이 높다. 따라서 나중에 분산 시스템으로 옮기려고 할 경우 작업이 어렵다.
- 도메인 분할 - 모듈러 모놀리스
- 도메인이나 워크 플로에 따라 아키텍처를 분할한다.
- 컴포넌트는 서로 중첩될 때가 많기 때문에 각 컴포넌트는 퍼시스턴트 라이브러리를 사용하거나 별도의 레이어에 비즈니스 규칙을 둘 수 있다.
- 도메인에 초점을 두어 프로젝트에서 가장 자주 발생하는 변경의 유형들이 더 확실하게 반영된다.
- 장점
- 세부 구현보다 비즈니스 기능에 더 가깝게 모델링된다.
- 역 콘웨이 전략을 활용하여 도메인별 다목적팀(cross-functional team)을 구성하기 쉽다.
- 모듈러 모놀리스와 마이크로서비스 아키텍처 스타일에 더 가깝다.
- 메시지 흐름이 문제 영역과 일치한다.
- 데이터와 컴포넌트를 분산 아키텍처로 옮기기 쉽다.
- 단점
- 유저 코드가 여기저기 널려있다.
컴포넌트 식별 흐름
컴포넌트 식별은 후보를 도출하고 피드백을 통해 다듬어가는 과정을 반복하는 것이 가장 좋다.
- 초기 컴포넌트 식별
- 아키텍트는 소프트웨어 프로젝트의 소스 코드가 생기기 전에 적용할 최상위 분할의 유형에 따라 최상위 컴포넌트를 어디서부터 시작할지 결정해야 한다.
- 요구사항을 컴포넌트에 할당
- 초기 컴포넌트를 식별한 후, 아키텍트는 컴포넌트 요구사항을 대입해서 잘 맞는지 확인한다.
- 역할 및 책임 분석
- 컴포넌트에 스토리를 대입할 때 아키텍트는 요구사항을 파악하는 단계에서 밝혀진 역할과 책임도 살펴보고 세분도가 적합한지 확인한다.
- 아키텍처 특성 분석
- 커포넌트에 요구상을 대입할 때 아키텍트는 앞서 식별한 아키텍처 특성들이 컴포넌트 분할 및 세분도에 어떤 역할을 미치는지 살펴봐야 한다.
- 컴포넌트 재구성
- 소프트웨어 프로젝트 도중 어떤 미지의 이슈가 튀어나올지 알 수 없다. 컴포넌트 설계를 반복 접근하는 방식이 정말 중요하다.
아키텍처 퀀틈 딜레마: 모놀리식 vs 분산 아키텍처
아키텍처 스타일은 저마다 트레이드 오프가 있다. 그러나 근본적인 설정으로 설계 프로세스 중에서 식별된 아키텍처 퀀텀 수에 죄우된다. 만약 시스템이 단일 퀀텀만으로 가능하다면 모놀리스 아키텍처가 장점이 더 많다. 반면 컴포넌트 아키텍처 특성이 모두 달라 지는 경우 이를 수용할 수 있는 분산 아키텍처가 필요하다. 아키텍처 퀀텀을 사용하면 초기 설계 단계에서 아키텍처의 근본적인 설계를 결정할 수 있으므로 아키텍처 특성 범위와 커플링을 분석하는 방법이 장점이다.
컴포넌트 식별 흐름
컴포넌트 식별은 후보를 도출하고 피드백을 통해 다듬어가는 과정을 반복하는 것이 가장 좋다.
- 초기 컴포넌트 식별
- 아키텍트는 소프트웨어 프로젝트의 소스 코드가 생기기 전에 적용할 최상위 분할의 유형에 따라 최상위 컴포넌트를 어디서부터 시작할지 결정해야 한다.
- 요구사항을 컴포넌트에 할당
- 초기 컴포넌트를 식별한 후, 아키텍트는 컴포넌트 요구사항을 대입해서 잘 맞는지 확인한다.
- 역할 및 책임 분석
- 컴포넌트에 스토리를 대입할 때 아키텍트는 요구사항을 파악하는 단계에서 밝혀진 역할과 책임도 살펴보고 세분도가 적합한지 확인한다.
- 아키텍처 특성 분석
- 컴포넌트에 요구상을 대입할 때 아키텍트는 앞서 식별한 아키텍처 특성들이 컴포넌트 분할 및 세분도에 어떤 역할을 미치는지 살펴봐야 한다.
- 컴포넌트 재구성
- 소프트웨어 프로젝트 도중 어떤 미지의 이슈가 튀어나올지 알 수 없다. 컴포넌트 설계를 반복 접근하는 방식이 정말 중요하다.
아키텍처 퀀텀 딜레마: 모놀리식 vs 분산 아키텍처
아키텍처 스타일은 저마다 트레이드 오프가 있다. 그러나 근본적인 설정으로 설계 프로세스 중에서 식별된 아키텍처 퀀텀 수에 죄우된다. 만약 시스템이 단일 퀀텀만으로 가능하다면 모놀리스 아키텍처가 장점이 더 많다. 반면 컴포넌트 아키텍처 특성이 모두 달라 지는 경우 이를 수용할 수 있는 분산 아키텍처가 필요하다. 아키텍처 퀀텀을 사용하면 초기 설계 단계에서 아키텍처의 근본적인 설계를 결정할 수 있으므로 아키텍처 특성 범위와 커플링을 분석하는 방법이 장점이다.
9 기초
기초적인 아키텍처 패턴을 익힌다. 구조적 측면과 어떤 종류의 아키텍처 특성이 알맞은지, 통상적인 배포 모델과 데이터 전략을 이해할 수 있다.
기초 패턴
진흙잡탕
실제 내부 구조라 할 만한 것은 없는, 데이터베이스를 직접 호출하는 이벤트 핸들러를 가진 단순한 스크립팅 애플리케이션이며, 아키텍처 안티패턴이다. 코드 품질 및 구조에 관한 거버넌스가 결여된 탓에 많은 프로젝트가 이렇게 된다.
유니터리 아키텍처
소프트웨어 태동기에 단 1대의 컴퓨터에서 동작하는 아키텍처이다.
클라이언트/서버
프론트엔드와 백엔드로 기술적 기능을 분리하는 대표적인 기본 아키텍처이다. 시대와 컴퓨팅 파워에 따라 여러 형테로 존재해왔다.
데스크톱 + 데이터베이스 서버
초창기 PC 아키텍처에서는 윈도우와 같은 UI를 기반으로 리치 데스크톱 애플리케이션을 개발하도록 지원했다. 프레젠테이션 로직은 데스크톱에 두고 계산량이 많은 액션은 별도의 사양이 탄탄한 데이터베이스 서버로 분리한다.
브라우저 + 웹서버
현대 웹 개발 시대에서 웹 브라우저가 웹 서버에 접속, 그리고 웹 서버는 데이터베이스 서버에 접속하는 형태가 일반화되었다. 데이터베이스와 웹 서버가 모두 운영 센터 내부의 동급 머신에서 운용되므로 이 구조를 2티어 아키텍처로 바라보는 아키텍트들도 있다.
3티어
1990년대 후반 인기를 끈 아키텍처이다. 고성능 데이터베이스 서버를 사용하는 데이터베이스 티어, 애플리케이션 서버가 관리하는 애플리케이션 티어, 프런트엔드 티어로 구성한다. 분산 아키텍처에 적합한 공통 객체요청 브로커 아키텍처(CORBA), 분산 컴포넌트 객체 모델(DCOM) 같은 네트워크 수준 프로토콜과 적합하다.
모놀리식 대 분산 아키텍처
아키텍처 스타일을 크게 분류하면 모놀리식과 분산형 두 종류이다.
모놀리식
- 레이어드 아키텍처
- 파이프라인 아키텍처
- 마이크로커널 아키텍처
분산형
- 서비스 기반 아키텍처
- 이벤트 기반 아키텍처
- 공간 기반 아키텍처
- 서비스 지향 아키텍처
- 마이크로서비스 아키텍처
분산 아키텍처는 성능, 확장성, 가용성 측면에서 강력하지만 트레이드 오프가 수반된다.
분산 컴퓨팅의 8가지 오류
- 오류 #1 : 네트워크는 믿을 수 있다
- 분산 아키텍처 특성상 서비스 간에 이동하는 네트워크에 의존하므로 시스템의 신뢰도가 떨어질 수 있다.
- → 타임아웃 같은 장치를 마련하거나 서비스 간 회로 차단기(서킷 브레이커)를 두는 방법이 있다.
- 오류 #2 : 레이턴시는 0이다
- 분산 아키텍처에서 원격 액세스 프로토콜을 통해 수행하면 레이턴시는 0이 될 수 없다.
- → 운영환경에서의 평균 레이턴시를 알아야 한다.
- 오류 #3 : 대역폭은 무한하다
- 마이크로서비스 분산 아키텍처에서 자잘한 서비스들 간에 주고 받는 통신이 대역폭을 상당히 점유하여 네트워크가 느려질 수 있다.
- 레이턴시(오류 #2)와 신뢰성(오류 #1)에 영향을 미친다.
- → 스탬프 커플링(stamp coupling)을 해결한다.
- 프라이빗 REST API 엔드포인트를 둔다.
- 계약에 필드 셀렉터를 사용한다.
- GraphQL로 계약을 분리한다.
- 컨슈머 주도 계약(CDC, consumer-driven contact)과 값 주도 계약(VBC, value-driven contact)을 병용한다.
- 내부 메시징 엔드포인트를 사용한다.
- 마이크로서비스 분산 아키텍처에서 자잘한 서비스들 간에 주고 받는 통신이 대역폭을 상당히 점유하여 네트워크가 느려질 수 있다.
- 오류 #4 : 네트워크는 안전하다
- 분산 아키텍처의 경우 더 넓은 영역이 악의적인 외부의 위협과 공격에 노출된다.
- 모든 엔드포인트에 보안이 적용되어야 하므로 성능이 떨어질 수밖에 없다.
- 오류 #5 : 토폴로지는 절대 안 바뀐다
- 네트워크를 구성하는 모든 라우터, 허브, 스위치, 방화벽, 네트워크, 어플라이언스 등 전체 네트워크 토폴로지는 변할 수 있다.
- → 아키텍트는 운영자, 네트워크 관리자와 항시 소통하며 무엇이, 언제 변경되었는지 알아야 한다.
- 오류 #6 : 관리자는 한 사람 뿐이다
- 분산 아키텍처에서 많은 소통과 협력이 필요하다.
- 오류 #7 : 운송비는 0이다
- 분산 아키텍처는 하드웨어, 서버, 게이트웨이, 방화벽, 신규 서브넷, 프록시 등 리소스가 더 많이 동원되므로 모놀리식 아키텍처보다 비용이 훨씬 많이 든다,.
- 오류 #8 : 네트워크는 균일하다
- 온갖 종류의 하드웨어가 모두 잘 맞물려 동작하는 것은 아니다.
- 네트워크 신뢰성, 레이턴시, 대역폭도 영향을 받을 수 있다.
또한 다음과 같은 문제점이 있다.
- 분산 로그
- 분산 로깅에 대한 솔루션과 패턴이 필요하다.
- 분산 트랜잭션
- 분산 트랜잭션을 관리하기 어렵다.
- 최종 일관성이라는 개념을 바탕으로 분리된 배포 단위에서 처리된 데이터를 알 수 없는 시점에 일관된 상태로 동기화한다. (확장성, 성능 가용성과 데이터 일관성, 무결성을 트레이드오프)
- 계약 관리 및 버저닝
- 분산 아키텍처는 분산된 서비스와 시스템을 제각기 다른 팀과 부서가 소유하기 때문에 계약 유지보수가 어렵다.
10 레이어드 아키텍처 스타일
가장 흔한 아키텍처 스타일중 하나이다. 콘웨이 법칙과 어울리고 단순하고 대중적이어서 모든 애플리케이션의 사실상 표준(de facto standard) 아키텍처이다.
토폴로지
내부 컴포넌트를 논리적으로 수평한 레이어들로 구성하며 일반적으로 아래의 4개 표준 레이어로 구성한다.
- 프레젠테이션 레이어
- 비즈니스 레이어
- 퍼시스턴트 레이어
- 데이터베이스 레이어
관심사의 분리 개념 덕분에 레이어드 아키텍처 스타일은 아키텍처 내부의 역할 및 책임 모델을 효과적으로 구성할 수 있다.
- 장점 : 개발자 본인의 기술 역량을 도메인 기술적인 부분에 집중시킬 수 있다.
- 단점 : 전체적인 민첩성이 떨어진다.
기술 역할에 따라 분리하기 때문에 도메인 주도 설계 방식과는 잘 맞지 않는다.
레이어 격리
레이어 격리란 어느 아키텍처 레이어에 변경이 일어나도 다른 레이어에 있는 컴포넌트는 영향을 받지 않기에 레이어 간 계약은 불변임을 의미한다.
레이어를 격리하면 모든 레이어를 다른 레이어에 영향을 주지 않고 교체할 수 있다.
레이어 추가
레이어를 개방하는 것이 유리한 때가 있다.
예를 들어 비즈니스 레이어에 구현한 공통 비즈니스 기능이 구현된 객체를 프레젠테이션 레이어에서 직접 사용할 수 없도록 아키텍처 결정을 했다고 가정하자. 개방/폐쇄 개념을 이용하여 서비스 레이어를 새로 추가하고, 비즈니스 레이어에 개방하여, 만약 비즈니스 레이어에서 퍼시스턴트 레이어에 접근한다면 통과하도록 설계한다.
개방/폐쇄 개념은 아키텍처 레이어 간 관계와 요청 흐름을 정의할 때 유용하다.
기타 고려사항
아키텍처 스타일을 결정하지 못했다면 레이어드 아키텍처가 좋은 출발점이 될 것이다.
레이어드 아키텍처에서는 아키텍처 싱크홀(architecture sinkhole)안티패턴을 조심해야 한다. 파레토 법칙(pareto principle)에 의해 전체 요청의 20%가 싱크홀 정도면 괜찮은 수준이다.
11 파이프라인 아키텍처 스타일
파이프라인 아키텍처는 애플리케이션 로직을 필터 타입에 따라 나누는 기술 분할 아키텍처이다. 파이프와 필터 아키텍처라고도 불린다.
기능을 개별 파트로 분리하기로 결정하는 순간부터 이 패턴이 수반된다.
Bash나 Zsh와 같은 유닉스 터미널 쉘 언어의 기초 원리이며, 함수형 언어와 연관이 깊다.
토폴로지
파이프라인 아키텍처는 다수의 파이프와 필터로 구성된다.
파이프
파이프란 한 소스에서 입력을 받아 다른 소스로 출력하는 필터 간 통신 채널이다. 성능상의 이유로 단방향, 점대점 방식으로 구성한다.
페이로드 데이터는 고성능에 유리한 적은 양의 데이터를 선호한다. 그러나 어떤 포맷이라도 가능하다.
필터
필터는 다른 필터와 독립적이며, 일반적으로 무상태성이다. 자기완비형(self-contained)이다. 필터는 한 가지 태스크만 수행하며, 여러 필터를 이어 붙여 복합 태스크를 수행할 수 있다.
파이프라인 아키텍처 스타일에서 네 가지 종류의 필터가 있다.
- 프로듀서
- 프로세스의 시작점이다.
- 아웃바운드만 있어서 소스라고도 한다.
- 변환기(transformer)
- 입력을 받아 필요시 일부 또는 전체 데이터를 변환한 후 결과를 아웃바운드 파이프로 전달한다.
- 함수형 프로그래머는 이 기능을 맵(map)이라고 부른다.
- 테스터
- 입력을 받아 테스트를 하고 결과에 따라 필요시 결과를 생산한다.
- 함수형 프로그래머는 이 기능을 리듀스(reduce)라고 부른다.
- 컨슈머
- 파이프라인 흐름의 종착역이다.
- 파이프라인 프로세스의 최종 결과를 데이터베이스에 저장하거나, 유저 인터페이스에 표시한다.
12 마이크로커널 아키텍처 스타일
마이크로커널 아키텍처는 플러그인 아키텍처라고도 한다.
토폴로지
마이크로커널 아키텍처 스타일은 코어 시스템과 플러그인 컴포넌트라는 두가지 아키텍처 요소로 구성된다.
애플리케이션 로직을 독립적인 플러그인 컴포넌트와 기본 코어 시스템에 분산하여 확장성, 적응성, 애플리케이션 기능 분리, 커스텀 처리 등을 수행한다.
코어 시스템
코어 시스템은 시스템을 실행시키는 데 필요한 최소한의 기능을 정의한다. 이는 순환 복잡도를 없애고 별도의 플러그인 컴포넌트를 장착하여 확장성, 유지보수성 그리고 시험성을 향상시킨다.
코어 시스템은 규모와 복잡성에 따라 레이어드 아키텍처나 모듈러 모놀리스로 구현할 수 있다. 경우에 따라 코어 시스템을 별도 배포하는 도메인 서비스로 나누어 서비스 별 도메인에 특정한 플러그인 컴포넌트를 둘 수 있다.
프레젠테이션 레이어는 코어 시스템에 내장하거나 별도의 UI를 구현하고 코어시스템은 백엔드 서비스를 제공한다.
플러그인
플러그인 컴포넌트는 특수한 처리 로직, 부가 기능, 그리고 코어 시스템을 개선/확장하기 위한 커스텀 코드가 구현된 스탠드얼론 컴포넌트이다.
플러그인 컴포넌트는 컴파일 기반 또는 런타임 기반으로 만들 수 있다.
- 컴파일 기반 플러그인 컴포넌트
- 관리하기 편하다.
- 그러나 변경, 추가, 삭제 시 전체 모놀리식 애플리케이션을 재배포해야 한다.
- 런타임 기반 플러그인 컴포넌트
- 런타임에 코어 시스템이나 다른 플러그인을 재배포하지 않고도 바로 추가/삭제가 가능하다.
- 자바 OSGI(Open Service Gateway Initiative), 펜로즈, 직소, 프리즘 등의 프레임워크를 통해 관리한다.
플러그인 컴포넌트는 코어 시스템과 일반적으로 점대점(point-to-point) 통신을 한다 즉, 코어 시스템에 플러그인을 연결하는 파이프는 대부분 플러그인 컴포넌트의 진입점 클래스를 호출하는 메서드나 함수 코드이다.
반드시 점대점 통신을 해야 하는 것은 아니다. 각 플러그인을 스탠드얼론 서비스 또는 컨테이너에 구현한 마이크로서비스로 만들어 REST나 메시징 등 다른 방법으로 기능을 호출하는 방법도 있다. 그러나 이 토폴로지는 코어 시스템이 모놀리식으로, 여전히 단일 아키텍처 퀀텀이다.
레지스트리
플러그인 레지스트리는 플러그인 명칭, 데이터 계약, 세부 원격 액세스 프로토콜 등 각 플러그인 모듈 정보를 코어 시스템에게 제공한다.
레지스트리는 코어 시스템이 소유한 내부 맵 구조일 수 있고, 레지스트리 및 디스커버리 도구(예, 아파치 주키퍼나 콘술)가 코어 시스템이나 외부 배포된 시스템에 내장된 구조일 수 있다.
13 서비스 기반 아키텍처 스타일
서비스 기반 아키텍처는 마이크로서비스 아키텍처 스타일의 일종이다.
토폴로지
별도로 배포된 유저 인터페이스와 원격 서비스(도메인 서비스) 그리고 모놀리스 데이터베이스로 이루어진 대규모 분산 레이어 구조이다.
서비스 배포 방식에서 컨테이너화가 필수는 아니다.
서비스는 원격 액세스 프로토콜로 유저 인터페이스 외부에서 접속할 수 있다. 프로토콜은 일반적으로 REST를 많이 사용하지만, 메시징, 원격 프로시저 호출(RPC) , SOAP도 사용 가능하다.
유저 인터페이스는 프록시나 게이트웨이로 구성된 API 레이어를 통해 서비스에 접속할 수 있지만, 대개 서비스 로케이터 패턴에 따라 유저 인터페이스, API 게이트웨이, 프록시에 내장된 유저 인터페이스를 직접 액세스 한다.
서비스 설계 및 세분도
서비스 기반 아키텍처 스타일의 도메인 서비스는 일반적으로 단위가 크기 때문에 API 퍼사드 레이어, 비즈니스 레이어, 퍼시스턴트 레이어로 구성된 레이어드 아키텍처 스타일로 설계하는 것이 일반적이다. 모듈러 모놀리스 아키텍처 스타일과 같이 서브도메인을 이용해 도메인을 분할하는 방법도 사용된다.
세분도
도메인 서비스는 세분도가 크기 때문에 단일 도메인 서비스에서 데이터 무결성을 보장하기 위해 ACID 데이터베이스 트랜잭션을 사용한다. 반면 마이크로서비스처럼 분산도가 높은 아키텍처는 서비스를 더 잘게 나누어 BASE(기본적 가용성 basic availability, 소프트 상태 soft state, 최종 일관성 eventual consistency) 분산 트랜잭션 기법을 사용한다. 최종 일관성을 기반으로 ACID 트랜잭션 레벨의 데이터 무결성은 지원하지 않는다.
반면 도메인 서비스는 변경 영향도가 크다는 트레이드 오프가 있다.
데이터베이스 분할
서비스 기반 아키텍처 스타일은 일반적으로 주어진 애플리케이션 콘텍스트에서 서비스 수가 적은 편이라 단일 모놀리식 데이터베이스를 공유한다.
그러나 데이터베이스 커플링은 테이블 스키마 변경 시 문제가 될 수 있다. 엔티티 객체가 공유하는 단일 공유 라이브러리를 사용하면 데이터베이스 테이블 하나를 바꾸어도 모든 서비스에 영향을 미친다.
변경 영향도와 리스크를 낮추는 방법은 연합 공유 라이브러리(federated shared library)를 통해 데이터베이스를 논리 분할하는 것이다. 이러한 방식은 특정 논리 도메인에 속한 테이블을 변경해도 해당 엔티티 객체가 포함된 해당 공유 라이브러리를 사용하는 서비스만 영향을 받을 뿐, 그 밖의 서비스는 영향을 받지 않는다. 또한 테이블과 해당 엔티티 객체의 변경 영향도를 낮추는 한가지 방법은, 공통 엔티티 객체를 버전 관리 시스템에서 락킹하고 수정 권한을 데이터베이스 팀에만 부여하는 것이다.
14 이벤트 기반 아키텍처 스타일
이벤트 기반 아키텍처는 확장성이 뛰어난 고성능 애플리케이션 개발에 유리한 비동기 분산 아키텍처 스타일이다. 이벤트를 비동기 수신 및 처리하는 별도의 이벤트 처리 컴포넌트들로 구성된다. 스탠드얼론 아키텍처 스타일 또는 이벤트 기반 마이크로 서비스 아키텍처와 같은 다른 아키텍처 스타일에 내장할 수 있다.
애플리케이션은 일반적으로 요청 기반 모델을 사용하여,
- 어떤 액션을 수행하도록 요청하면 요청 오케스트레이터가 접수한다.
- 요청 오케스트레이터는 다양한 요청 프로세서에 확정적이고 동기적으로 요청을 전달한다.
- 요청 프로세서는 요청을 받아 데이터베이스에서 정보 조회 수정 등을 통해 작업을 수행한다.
반면, 이벤트 기반 모델은 특정 상황에 대응하여 이벤트에 알맞은 액션을 취한다.
토폴로지
이벤트 기반 아키텍처의 주요 토폴로지는 브로커 토폴로지와 중재자 토폴로지로 나뉜다.
브로커 토폴로지
신속한 응답과 동적인 이벤트 처리 제어가 필요할 때
- 중앙에 이벤트 중재자가 없다.
- 경량 메시지 브로커(RabbitMQ, ActiveMQ, HornetQ 등)를 통해 메시지를 브로드캐스팅한다.
- 비교적 이벤트 처리 흐름이 단순하고, 중앙에서 이벤트를 조정할 필요가 없을 때 사용한다.
아래와 같이 구성된다
- 이벤트 브로커
- 시작 이벤트를 받는다.
- 시작 이벤트 - 전체 이벤트의 흐름을 개시하는 이벤트
- 이벤트 브로커의 이벤트 채널이 시작 이벤트를 처리한다.
- 시작 이벤트를 받는다.
- 이벤트 프로세서
- 이벤트 브로커에서 시작 이벤트를 받아 관련된 처리 작업을 마친다.
- 처리 이벤트를 생성한다.
- 시스템에 비동기적으로 한 일을 알린다.
이 작업은 최종 이벤트 프로세서가 한 일에 아무도 관심이 없을 때 까지 반복된다.
브로커 토폴로지에서는 다른 이벤트 프로세서의 관심 여부와 무관하게 각 이벤트 프로세서가 자신이 한 일을 모두에게 알리는 것이 아키텍처 확장성 측면에서 바람직하다.
장점
- 이벤트 프로세서가 디커플링된다
- 확장성이 높다
- 응답성이 우수하다
- 성능이 우수하다
- 내고장성이 뛰어나다
단점
- 시작 이벤트와 연괸된 전체 워크플로를 제어할 수 없다.
- 특정 트랜잭션이 언제 끝났는지 모른다.
- 에러 처리가 어렵다.
- (중재자가 없기 때문에) 처리에 실패해도 다른 파트는 그 사실을 모른다.
중재자 토폴로지
이벤트 처리 워크플로를 제어해야할 때
이벤트 중재자를 사용하며 브로커 토폴로지의 단점을 보완한다.
- 이벤트 중재자
- 이벤트 큐에서 시작 이벤트를 받는다.
- 점대점 메시징으로 각각의 이벤트 채널로 처리 이벤트를 생성한다.
- 이벤트 프로세서
- 각 이벤트 프로세서는 이벤트 채널에서 이벤트를 받아 처리한다.
- 작업 완료후 중재자에게 응답한다.
장점
- 토폴로지의 단일 장애점(SPF, single point of failure)을 줄이고 전체 처리량과 성능을 높인다.
- 에러 처리와 오케스트레이션이 필요한 서비스는 중재자로 충분하다.
- 알맞은 중재자를 찾는 것이 중요하다.
- 아파치 카멜, 뮬, ESB, 스프링 인티그레이션
- 워크플로에 대해 잘 알고 있고 통제가 가능하다. 복구, 재시작이 가능하다.
단점
- 이벤트 프로세서가 커플링된다
- 확장성이 낮다
- 성능이 낮다
- 내고장성이 좋지 않다
- 워크플로 모델링이 복잡하다
두 토폴로지는 처리 이벤트의 의미와 사용 방법이 본질적으로 다르다.
브로커 토폴로지
- 처리 이벤트 발행 후, 이벤트 프로세서는 각자 맡은 일을 하며 나머지 이벤트 프로세서는 액션에 반응한다.
중재자 토폴로지
- 이벤트는 사건이 아니라 일어나야 할 일(커맨드)이다.
비동기 통신
이벤트 기반 아키텍처 스타일은 요청/응답 처리 및 파이어 엔드 포겟 처리까지 모두 비동기 통신만 사용한다. 이는 시스템 응답성을 높일 수 있는 강점으로 활용된다.
응답성과 성능
- 응답성
- 어떤 액션이 접수되어 곧 처리될 것을 유저에게 알림
- 성능
- 종단간 프로세스가 더 빨리 수행되게끔 만드는 것
에러 처리
비동기 통신은 응답성을 개선하지만 에러 처리는 어려워 시스템의 복잡도가 증가한다. 워크플로 이벤트 패턴을 사용하여 시스템 응답성에 영향을 미치지 않고 에러 처리를 개선할 수 있다.
- 이벤트 프로듀서는 메시지 채널을 통해 데이터를 이벤트 컨슈머에 비동기 전송한다.
- 이벤트 컨슈머가 데이터를 처리하는 도중 에러가 발생하면 즉시 해당 에러를 워크플로 프로세서에 위임한다.
- 이벤트 컨슈머는 다음 메시지로 넘어간다. (메시지 응답성)
- 이벤트를 수신한 워크플로 프로세서는 (사람의 개입 없이) 프로그래밍 방식으로 원데이터를 변경하여 긴급 조치한 후 원래 큐로 돌려보낸다.
- 문제점을 파악할 수 없는 경우 메시지를 다른 큐로 보내 대시보드가 받도록 한다.
- 이벤트 컨슈머는 이 메시지를 새로운 메시지로 간주, 재처리를 시도한다.
주의해야할 점은 메시지의 순서가 변경된다는 점이다. 한가지 해결 방법은 서비스가 에러가 발생한 번호를 붙여 큐에 담아 보관하는 것이다. 에러 조치되면 서비스는 큐에서 꺼내 처리한다.
Comments powered by Disqus.