4장에서는 아키텍처 특성이 무엇인지, 5장에서는 우리 시스템에서 중요한 아키텍처 특성을 어떻게 찾아내는지를 정리했다.
그런데 여기서 한 가지 문제가 생긴다.
우리 서비스는 성능이 중요합니다.
확장성이 좋아야 합니다.
유지보수하기 쉬워야 합니다.말은 좋은데, 이걸로는 실제 시스템이 잘 만들어졌는지 판단하기 어렵다.
"성능이 좋다"는 정확히 얼마나 빨라야 한다는 걸까?
"유지보수하기 쉽다"는 어떻게 확인하지?
그리고 더 중요한 문제.
처음에는 잘 지켜졌던 아키텍처가 개발을 계속하면서 망가지고 있다면 어떻게 알아낼까?
6장은 결국 이 두 가지 질문을 다룬다.
아키텍처 특성을 어떻게 측정할 것인가?
그리고 그 특성이 시간이 지나도 유지되게 어떻게 관리할 것인가?
"성능이 좋아야 한다"는 측정할 수 없다
예를 들어 요구사항이 이렇다고 해보자.
우리 API는 빨라야 한다.이건 사실 거의 아무 의미가 없다.
누군가에게 빠르다는 건 1초일 수도 있고, 다른 사람에게는 100ms일 수도 있다.
조금 더 구체적으로 만들어보자.
API 평균 응답 시간이 300ms 이하여야 한다.훨씬 좋아졌다.
하지만 이것도 아직 문제가 있다.
평균이 300ms라고 해도
99개의 요청 → 100ms
1개의 요청 → 20초라면 정말 성능이 좋은 시스템일까?
그래서 실제로는
전체 요청의 95%가 300ms 이내에 응답해야 한다.처럼 정의할 수 있다.
이제야 비로소
측정할 수 있고
테스트할 수 있고
기준을 넘었는지 판단할 수 있다.즉 아키텍처 특성도 가능하면 객관적인 숫자나 규칙으로 바꿔야 한다.
아키텍처 특성을 정의하기 어려운 이유
책에서는 아키텍처 특성을 측정하기 어려운 이유를 몇 가지 이야기한다.
첫 번째는 아키텍처 특성이 물리 법칙처럼 정확한 개념이 아니기 때문이다.
예를 들어
Agility
Maintainability
Deployability
Performance같은 단어는 굉장히 많이 사용하지만, 사람마다 생각하는 의미가 조금씩 다르다.
특히
"성능이 중요합니다."라고 했을 때 개발자와 인프라 엔지니어가 생각하는 성능이 다를 수도 있다.
개발자
→ API 응답 속도
DB 엔지니어
→ Query 처리 시간
인프라 엔지니어
→ Throughput / CPU / Memory
사용자
→ 화면이 얼마나 빨리 보이는가같은 Performance라는 단어를 사용하면서 완전히 다른 이야기를 하고 있는 셈이다.
어떤 특성은 너무 큰 개념이다
또 하나의 문제는 하나의 아키텍처 특성이 사실 여러 특성의 묶음인 경우다.
대표적으로
Agility를 생각해보자.
"변화에 빠르게 대응할 수 있는 시스템"이라고 말할 수 있지만, 실제로는
Modularity
Deployability
Testability
Maintainability같은 여러 특성이 합쳐져야 만들어지는 성질이다.
즉
Agility를 높인다.라고 말하면 너무 추상적이다.
차라리
각 서비스는 독립적으로 배포할 수 있어야 한다.
핵심 모듈의 테스트 커버리지는 일정 기준 이상이어야 한다.
모듈 간 순환 의존성이 없어야 한다.처럼 더 작은 단위로 쪼개야 실제로 관리할 수 있다.
측정할 수 없는 특성은 관리하기도 어렵다.
1. Operational Measures
가장 직관적으로 측정하기 쉬운 건 운영 관련 특성이다.
예를 들어
Performance
Scalability
Availability
Reliability같은 것들.
이런 특성은 실제 시스템에서 숫자로 표현하기 비교적 쉽다.
Performance
p95 응답 시간 < 300ms
p99 응답 시간 < 800msAvailability
월간 가용성 ≥ 99.99%Scalability
동시 사용자 100,000명 상황에서도
p95 응답 시간이 500ms를 넘지 않는다.Reliability
메시지 처리 실패율 < 0.01%이렇게 만들면
"우리 시스템 성능 괜찮은 것 같은데?"같은 감각적인 판단이 아니라 실제 데이터로 이야기할 수 있다.
측정할 때는 평균만 보면 위험하다
성능을 측정할 때 특히 평균값만 보는 것은 문제가 될 수 있다.
예를 들어 API 응답 시간이
평균 200ms라고 하자.
겉으로는 좋아 보인다.
하지만 실제 분포가
50% → 50ms
40% → 100ms
9% → 500ms
1% → 15초라면 일부 사용자는 매우 나쁜 경험을 하게 된다.
그래서 성능에서는 평균보다
p50
p95
p99같은 percentile을 자주 사용한다.
p95 = 전체 요청 중 95%가 이 시간 안에 처리됨아키텍처 특성을 측정할 때 중요한 건 결국
그 특성을 가장 잘 표현하는 측정 방법을 선택하는 것
이다.
2. Structural Measures
그런데 모든 아키텍처 특성을 서버 메트릭처럼 측정할 수 있는 것은 아니다.
예를 들어
Maintainability
Modularity
Code Quality는 어떻게 측정할까?
여기서는 코드의 구조 자체를 측정해야 한다.
대표적인 예가 Cyclomatic Complexity다.
함수 안에 분기문이 많아질수록 가능한 실행 경로가 많아진다.
function getPrice(user) {
if (user.isPremium) {
if (user.hasCoupon) {
// ...
}
}
if (user.country === "KR") {
// ...
}
}조건이 많아질수록 코드가 이해하기 어려워지고, 테스트해야 할 경로도 늘어난다.
그래서 이런 식의 기준을 정할 수 있다.
Cyclomatic Complexity <= 10기준을 넘어가는 코드가 생기면 리팩터링 대상으로 보는 것이다.
의존성도 측정할 수 있다
구조적으로 중요한 또 다른 요소는 모듈 간 의존성이다.
예를 들어 우리가 계층형 아키텍처를 사용한다고 해보자.
Presentation
↓
Business
↓
Persistence우리가 원하는 규칙은
Presentation → Business 가능
Business → Persistence 가능
Persistence → Presentation 금지일 수 있다.
그런데 개발을 하다 보면 누군가 편의를 위해 이런 코드를 만들 수 있다.
Persistence
↓
Presentation코드는 정상적으로 컴파일되고 서비스도 잘 돌아갈 수 있다.
하지만 아키텍처 관점에서는 구조가 망가진 것이다.
이런 것도 테스트할 수 있다.
Persistence 패키지는
Presentation 패키지를 import할 수 없다.즉 구조적 아키텍처 특성도 검증 가능한 규칙으로 바꿀 수 있다.
3. Process Measures
세 번째는 개발 과정에 관련된 특성이다.
대표적으로
Deployability
Testability
Agility같은 것들.
예를 들어 Deployability가 중요하다고 해보자.
그냥
우리 시스템은 배포하기 쉬워야 한다.라고 말하기보다
배포 빈도
배포에 걸리는 시간
배포 실패율
Rollback에 걸리는 시간을 측정할 수 있다.
예를 들어
평균 배포 시간 < 10분
배포 실패율 < 2%
Rollback 가능 시간 < 5분처럼 정의하는 것이다.
이렇게 하면 애매했던 Deployability라는 특성이 실제 팀이 관리할 수 있는 지표가 된다.
그런데 측정만 하면 끝일까?
여기서 다음 문제가 생긴다.
처음 시스템을 만들었을 때
p95 < 300ms를 만족했다고 해보자.
6개월 뒤 새로운 기능이 계속 추가된다.
300ms
↓
350ms
↓
420ms
↓
600ms아무도 눈치채지 못한다면?
아키텍처 특성은 처음 설계할 때 한 번 확인한다고 끝나는 것이 아니다.
시스템은 계속 변하기 때문이다.
그래서 필요한 것이 Architecture Governance다.
Architecture Governance
거버넌스라고 하면 약간 거창하게 들리지만, 핵심은 단순하다.
우리가 중요하다고 정한 아키텍처 특성이 실제 개발 과정에서도 계속 지켜지고 있는지 관리하는 것.
예를 들어 팀에서 이런 규칙을 만들었다고 하자.
Presentation Layer는
Persistence Layer를 직접 호출하면 안 된다.문서에만 적어두면 어떻게 될까?
architecture.md
Presentation → Business → Persistence몇 달 뒤 급한 기능을 만드는 개발자가
import { userRepository } from "../persistence";를 넣는다.
서비스는 정상적으로 돌아간다.
테스트도 통과한다.
그리고 조금씩
Presentation ───────→ Persistence
↓
Business
↓
Persistence구조가 망가지기 시작한다.
이런 현상을 흔히 Architecture Drift, 즉 처음 의도한 아키텍처에서 조금씩 벗어나는 현상으로 볼 수 있다.
문서보다 코드로 지키는 게 강하다
이 문제를 해결하는 가장 좋은 방법 중 하나는
"이렇게 개발해주세요."라고 문서에 쓰는 것보다
"이 규칙을 어기면 CI가 실패한다."로 만드는 것이다.
예를 들어
Presentation → Persistence 직접 참조 금지를 자동화된 테스트로 만들 수 있다.
Architecture Test
Persistence에 대한 직접 의존성이 존재하는가?
YES → CI FAIL
NO → PASS이제 규칙이 사람의 기억에 의존하지 않는다.
이런 개념이 Fitness Function으로 연결된다.
Fitness Function
책에서 굉장히 중요한 개념 중 하나다.
Fitness Function은 간단히 말하면
아키텍처 특성이 우리가 원하는 상태를 유지하고 있는지 객관적으로 평가하는 장치
다.
예를 들어 Performance가 중요한 시스템이라면
p95 response time < 300ms라는 Fitness Function을 만들 수 있다.
CI에서 성능 테스트를 실행하고
p95 = 270ms → PASS
p95 = 430ms → FAIL하도록 만드는 것이다.
코드 구조도 Fitness Function이 될 수 있다
예를 들어 우리가 이런 규칙을 중요하게 생각한다고 해보자.
도메인 모듈끼리 순환 의존성이 없어야 한다.그러면
A → B
B → C
C → A같은 구조가 생기면 테스트를 실패시키면 된다.
Circular Dependency 발견
→ Fitness Function FAIL또는
특정 모듈의 Cyclomatic Complexity는
20을 넘으면 안 된다.같은 규칙도 가능하다.
즉 Fitness Function은 반드시 성능 테스트일 필요가 없다.
Performance Test
Dependency Test
Security Test
Code Metric
Deployment Metric
Infrastructure Policy등 아키텍처 특성을 객관적으로 평가할 수 있는 모든 메커니즘이 될 수 있다.
중요한 건 "지속적으로" 확인하는 것
Fitness Function의 중요한 점은 한 번 측정하는 게 아니다.
개발 과정 안에 들어가야 한다.
Developer
↓
Commit
↓
CI
↓
Unit Test
Architecture Test
Performance Test
Security Test
↓
Deploy예를 들어 PR 하나가 들어올 때마다
순환 의존성이 생기지 않았는가?
레이어 규칙을 위반하지 않았는가?
성능 기준을 넘지 않았는가?를 자동으로 확인한다.
그러면 아키텍처는 더 이상
처음에 설계하고 끝나는 것이 아니라
계속 검증되는 시스템의 속성이 된다.
모든 것을 자동화할 수 있는 건 아니다
그렇다고 모든 아키텍처 특성을 숫자로 만들 수 있는 것은 아니다.
예를 들어
코드가 이해하기 쉬운가?
API 설계가 직관적인가?
도메인 경계가 적절한가?같은 것은 완전히 자동화하기 어렵다.
이런 부분은
Code Review
Architecture Review
ADR
Design Review등 사람의 판단이 여전히 필요하다.
그래서 거버넌스는
Automation
+
Human Review를 적절하게 섞는 과정이라고 볼 수 있다.
가능한 것은 자동화하고, 판단이 필요한 것은 사람이 검토한다.
중요한 건 규칙을 만드는 것보다 피드백 루프를 만드는 것
처음에는 아키텍처 거버넌스라고 하면
아키텍트가 규칙을 만든다.
개발자는 그 규칙을 따른다.같은 느낌을 떠올릴 수도 있다.
그런데 이 장을 읽다 보면 핵심은 통제라기보다 피드백 루프에 가까운 것 같다.
Architecture Decision
↓
측정 가능한 기준 정의
↓
시스템 변경
↓
Fitness Function
↓
기준을 만족하는가?
↓
Architecture Decision 재검토시스템은 계속 변하고, 비즈니스도 계속 변한다.
그러니 아키텍처 특성 역시
한 번 결정하고 끝이 아니라
측정
→ 확인
→ 변화
→ 다시 측정하는 과정이 필요하다.
프론트엔드에서도 똑같이 생각할 수 있다
이 개념은 백엔드나 인프라에만 적용되는 이야기가 아니다.
예를 들어 큰 프론트엔드 프로젝트에서
Performance
Maintainability
Modularity
Accessibility를 중요하게 본다고 해보자.
그러면 이런 Fitness Function을 만들 수 있다.
Performance
LCP < 2.5s
JS Bundle < 300KB
특정 Interaction < 200msModularity
feature 모듈끼리 직접 참조 금지
순환 의존성 금지Maintainability
Cyclomatic Complexity < N
특정 모듈 크기 제한Accessibility
axe 검사에서 critical violation = 0그리고 CI에서 계속 검사한다.
PR
↓
Lint
↓
Unit Test
↓
Architecture Test
↓
Bundle Size Check
↓
Accessibility Test이렇게 생각하면 Fitness Function이라는 개념이 훨씬 익숙해진다.
사실 우리가 이미 사용하고 있는
ESLint
TypeScript
Bundle Size Check
Lighthouse CI
Dependency Cruiser
Architecture Test같은 도구들도 관점에 따라서는 아키텍처 거버넌스를 구현하는 장치라고 볼 수 있다.
결국 측정할 수 있어야 지킬 수 있다
4장부터 흐름을 연결하면 꽤 깔끔하다.
4장
Architecture Characteristics Defined
"아키텍처 특성이 무엇인가?"
↓
5장
Identifying Architecture Characteristics
"우리 시스템에서 무엇이 중요한가?"
↓
6장
Measuring & Governing Architecture Characteristics
"그걸 어떻게 측정하고 계속 지킬 것인가?"결국
Performance가 중요하다.에서 끝나는 게 아니라
Performance가 중요하다.
↓
p95 응답 시간이 300ms 이하여야 한다.
↓
지속적으로 측정한다.
↓
기준을 넘으면 알림 또는 CI 실패.
↓
필요하면 구조를 다시 검토한다.까지 가야 실제 아키텍처 요구사항이 된다.
결국 6장은 "아키텍처를 실행 가능한 규칙으로 만드는 법" 같다
개인적으로 6장에서 가장 인상적인 부분은 아키텍처를 문서에만 존재하는 설계에서 실제로 검증 가능한 것으로 바꾸는 것이었다.
"우리 시스템은 모듈성이 좋아야 합니다."
"성능이 중요합니다."
"레이어를 잘 지켜주세요."라고 말하는 건 쉽다.
하지만 실제 개발에서는 이것을
무엇을 측정할 것인가?
어떤 값을 정상이라고 볼 것인가?
언제 측정할 것인가?
기준을 어기면 어떻게 알 것인가?
자동화할 수 있는가?까지 내려가야 한다.
결국
아키텍처 특성은 선언하는 것보다 측정하고 지속적으로 검증할 때 실제 의미가 생긴다.
4장에서 무엇을 중요하게 볼지 정의하고, 5장에서 우리 시스템에서 중요한 것을 골랐다면, 6장은 그것을 코드와 개발 프로세스 안에서 실제로 살아 있게 만드는 방법에 대한 이야기인 듯.
레퍼런스
- Mark Richards, Neal Ford, Fundamentals of Software Architecture — Chapter 6, Measuring and Governing Architecture Characteristics