graph TD
A["(1) 무엇이 비즈니스에 가장 중요한가? (Architecture Driver)"] --> B["(2) 시스템이 얼마나 안정적이고 빨라야 하는가? (Quality Attribute)"]
B --> C["(3) 그 목표를 어떤 원리로 달성할 것인가? (Tactic & Pattern)"]
C --> D["(4) 무엇을 얻고 무엇을 희생할 것인가? (Trade-off 분석)"]
D --> E["(5) 최종 선택의 이유와 맥락을 기록했는가? (ADR 작성)"]
style A fill:#E0F2FE,stroke:#0284C7,stroke-width:2px;
style B fill:#F0FDF4,stroke:#16A34A,stroke-width:2px;
style C fill:#FEF3C7,stroke:#D97706,stroke-width:2px;
style D fill:#FEE2E2,stroke:#DC2626,stroke-width:2px;
style E fill:#F3E8FF,stroke:#9333EA,stroke-width:2px;
0. 도구 우선(Tool-First) 사고의 함정: Redis와 Kafka부터 고르지 마라
주니어 개발자들이 시스템 설계를 시작할 때 가장 흔히 범하는 실수가 있습니다. 요구사항을 듣자마자 “어? 캐시로 Redis 붙이고, 메시지 큐로 Kafka 쓰고, 서버는 마이크로서비스(MSA)로 쪼갠 다음 쿠버네티스에 올리죠!”라고 외치는 것입니다.
이것이 바로 전형적인 ‘도구 우선(Tool-First) 사고의 함정’입니다.
소프트웨어 아키텍처는 유행하는 오픈소스 도구들을 레고 블록처럼 이어 붙이는 기술이 아닙니다. 진정한 아키텍처는 다음과 같은 냉정한 질문에 답하는 체계적인 엔지니어링 과정입니다:
아키텍처의 3대 계층: 전술(Tactic) vs 패턴(Pattern) vs 스타일(Style)
많은 개발자가 이 용어들을 뒤섞어 사용합니다. 하지만 이 셋은 추상화의 수준이 완전히 다릅니다.
| 구분 | 개념 정의 | 비유 (도시 건축) | 소프트웨어 실무 예시 |
|---|---|---|---|
| 아키텍처 스타일 (Style) | 시스템 전체의 거시적 구조와 통신 철학 | “우리는 격자형 계획도시로 짓겠다.” | 모놀리식, 마이크로서비스(MSA), 파이프-필터, 이벤트 기반(EDA) |
| 아키텍처 패턴 (Pattern) | 특정 서브시스템의 문제를 해결하는 입증된 구조적 해법 | “상업 지구에는 고층 복합 상가 건물을 짓겠다.” | 헥사고날(포트-어댑터), CQRS, 레이어드 아키텍처 |
| 아키텍처 전술 (Tactic) | 단 하나의 품질 속성(성능/가용성)을 달성하기 위한 구체적 설계 테크닉 | “정전에 대비해 건물 지하에 비상 발전기를 두겠다.” | 하트비트 핑, 지수 백오프 재시도, 커넥션 풀링, 캐싱 |
8가지 핵심 소프트웨어 아키텍처 원칙
원칙 1. 관심사의 분리 (Separation of Concerns, SoC)
핵심 개념: 하나의 소프트웨어 모듈은 단 하나의 명확한 관심사(비즈니스 규칙, 화면 렌더링, 데이터베이스 저장, 외부 API 통신 등)에만 집중해야 합니다.
안티패턴: 컨트롤러(Controller) 안에서 SQL 쿼리를 직접 날리고, 외부 PG사 결제 API를 호출하며, 이메일 발송까지 한 함수에서 전부 처리하는 ‘신(God) 객체’ 안티패턴.
해결책:
[HTTP 요청] -> Controller(요청 파싱) -> Service(순수 비즈니스 규칙) -> Repository(DB 영속화)관심사를 엄격히 분리하면 DB가 오라클에서 MySQL로 바뀌어도 비즈니스 서비스 로직은 단 한 줄도 손대지 않아도 됩니다.
원칙 2. 높은 응집도와 낮은 결합도 (High Cohesion & Low Coupling)
- 핵심 개념:
- 응집도(Cohesion): 모듈 내부의 요소들이 서로 얼마나 긴밀하게 관련되어 있는가. (하나의 모듈은 오직 하나의 역할을 완벽히 수행해야 함)
- 결합도(Coupling): 모듈과 모듈 사이의 의존성이 얼마나 느슨한가. (한 모듈의 수정이 다른 모듈에 파급을 일으키지 않아야 함)
- 실무 지침: 주문 모듈을 수정했는데 전혀 상관없는 회원 포인트 모듈에서 런타임 에러가 터진다면, 두 모듈 사이에 보이지 않는 공유 메모리나 전역 변수 같은 강결합이 존재한다는 명백한 위험 신호입니다.
원칙 3. 의존성 역전 원칙 (DIP) & 헥사고날 아키텍처 (Ports & Adapters)
- 핵심 개념: 고수준 모듈(핵심 비즈니스 도메인)은 저수준 모듈(구체적인 DB 라이브러리, 프레임워크)에 의존해서는 안 되며, 둘 다 추상화(인터페이스)에 의존해야 합니다.
graph TD
subgraph Outer["외부 인프라 영역 (어댑터 Adapters)"]
Web["REST API 컨트롤러 (Inbound)"]
DB["PostgreSQL / JPA (Outbound)"]
PG["토스페이먼츠 결제 연동 (Outbound)"]
end
subgraph Core["내부 순수 도메인 코어 (Hexagon Core)"]
PortIn["인바운드 포트 (OrderUseCase 인터페이스)"]
Domain["순수 비즈니스 로직 (Order Entity & Logic)"]
PortOut1["아웃바운드 포트 (OrderRepository 인터페이스)"]
PortOut2["아웃바운드 포트 (PaymentGateway 인터페이스)"]
end
Web --> PortIn --> Domain
Domain --> PortOut1
Domain --> PortOut2
DB -.->|구현 Implementation| PortOut1
PG -.->|구현 Implementation| PortOut2
style Core fill:#DCFCE7,stroke:#16A34A,stroke-width:2px;
style Outer fill:#F1F5F9,stroke:#475569,stroke-width:1px;
- 헥사고날의 혁신: 핵심 도메인 코드는 순수한 언어(Java, Python 등) 표준 라이브러리만 사용합니다. DB나 웹 프레임워크는 바깥 어댑터에 불과하므로, 단위 테스트 시 복잡한 DB 모킹(Mocking) 없이 초고속 인메모리 테스트가 가능합니다.
원칙 4. 명시적 경계와 계약 기반 설계 (Design by Contract)
- 핵심 개념: 서비스나 모듈 간의 소통은 암묵적인 가정에 기대지 않고, 엄격한 인터페이스 계약(API 스키마, 사전조건 Precondition, 사후조건 Postcondition, 불변식 Invariant)을 통해 이루어져야 합니다.
- 실무 실천:
- REST API/gRPC에서 OpenAPI(Swagger) 또는 Protocol Buffers 스키마를 단일 진실의 원천(SSOT)으로 삼기.
- 도메인 모델 생성 시 불변식을 강제하는 값 객체(Value Object) 패턴 적용 (예: 음수 금액을 생성 단계에서 원천 차단).
원칙 5. 무상태성(Statelessness)과 수평 확장성 (Scale-Out)
- 핵심 개념: 애플리케이션 서버 인스턴스는 세션(Session)이나 사용자 인증 상태를 자체 로컬 메모리에 보관해서는 안 됩니다.
- 아키텍처 구조:
- 모든 사용자 요청은 독립적이어야 하며, 서버 10대 중 어느 서버에 도달하든 동일한 결과를 낼 수 있어야 합니다.
- 로그인 세션과 토큰 캐시는 분산 인메모리 저장소(Redis)에 외주를 주고, 정적 파일은 CDN 엣지로 밀어냅니다.
- 결과적으로 트래픽 폭증 시 Auto-Scaling을 통해 서버 인스턴스를 수초 만에 1대에서 100대로 무제한 수평 증설(Scale-Out)할 수 있습니다.
원칙 6. 결함 감내와 장애 격리 (Fault Tolerance: Circuit Breaker & Bulkhead)
- 핵심 개념: 분산 시스템에서 장애는 “일어날 수도 있는 예외”가 아니라 “반드시 일어나는 일상적 상태”로 간주하고 설계해야 합니다.
- 핵심 전술 3종:
- 서킷 브레이커 (Circuit Breaker): 외부 결제 API의 응답 지연이 길어지면 회로를 강제로 끊어(Open) 즉시 기본값(Fallback)을 응답함으로써, 내 서버의 모든 작업 스레드가 대기 상태로 고갈되는 연쇄 장애(Cascading Failure)를 차단합니다.
- 벌크헤드 (Bulkhead, 선박 격벽): 한 기능(예: 추천 시스템)의 장애가 핵심 기능(예: 결제/주문)으로 번지지 않도록 스레드 풀과 리소스를 물리적으로 격리합니다.
- 지수 백오프와 지터 (Exponential Backoff with Jitter): 일시적 네트워크 장애 시
1초, 2초, 4초...에 무작위 시간을 섞어 재시도함으로써 대상 서버에 가해지는 트래픽 폭풍(Thundering Herd)을 방지합니다.
원칙 7. 명령과 조회의 책임 분리 (CQRS) 및 최종 일관성
- 핵심 개념: 상태를 변경하는 명령(Command: 쓰기/수정/삭제)과 상태를 반환하는 조회(Query: 읽기)는 처리 목적과 성능 특성이 상반되므로 데이터 모델을 분리해야 합니다.
graph LR
subgraph WritePath["쓰기 파이프라인 (Command)"]
Cmd["주문/결제 요청"] --> W_Model["정규화 RDBMS (PostgreSQL)<br>- ACID 트랜잭션 무결성 최우선"]
end
W_Model -->|도메인 이벤트 발행| Kafka["Kafka 이벤트 브로커"]
subgraph ReadPath["조회 파이프라인 (Query)"]
Kafka --> Cons["이벤트 컨슈머"] --> R_Model["비정규화 캐시 (Redis / Elasticsearch)<br>- 조인(JOIN) 0개 즉시 반환"]
Client["화면 조회 요청"] --> R_Model
end
style WritePath fill:#FEF3C7,stroke:#D97706,stroke-width:1px;
style ReadPath fill:#DCFCE7,stroke:#16A34A,stroke-width:1px;
- 최종 일관성 (Eventual Consistency): 쓰기 DB와 조회 캐시 사이에 0.1~0.5초의 미세한 동기화 시차(Lag)를 허용하는 대신, 수천만 명의 사용자가 동시에 메인 페이지를 조회해도 DB가 뻗지 않는 압도적인 읽기 처리량을 확보합니다.
원칙 8. 진화적 아키텍처와 결정의 기록 (Evolutionary Architecture & ADR)
- 핵심 개념: 초기부터 모든 것을 예측한 완벽한 아키텍처는 환상입니다. 요구사항의 변화에 따라 부드럽게 변형될 수 있는 유연성을 유지하고, “왜 이 결정을 내렸는가?”를 문서(ADR)로 영구 보존해야 합니다.
힌트📝 실무 ADR (Architecture Decision Record) 필수 5대 포맷
- 제목 (Title):
ADR-007: 실시간 알림 파이프라인으로 Redis Pub/Sub 채택 - 맥락 (Context): 동시 접속자 10만 명에게 지연 시간 200ms 이내로 실시간 알림을 쏴야 하는 기술적 요구 발생.
- 고려한 대안 (Options): 대안 A: Kafka, 대안 B: RabbitMQ, 대안 C: Redis Pub/Sub
- 최종 결정과 이유 (Decision): 메시지 영속성보다는 초경량 처리 속도와 기존 인프라 운영 복잡도 최소화를 위해 Redis Pub/Sub 채택.
- 트레이드오프 및 재검토 조건 (Consequences & Revisit): 서버 재부팅 시 메시지 유실 가능성을 감수함. 메시지 유실 방지가 필수적인 금융 알림으로 확장될 경우 Kafka로 교체 검토.
아키텍처 스타일별 트레이드오프 총괄 매트릭스
| 아키텍처 스타일 | 우리가 얻는 가치 (+) | 우리가 치러야 할 대가 (-) | 추천 비즈니스 상황 |
|---|---|---|---|
| 모놀리식 (Monolithic) | 단순한 개발·배포, 강력한 ACID 트랜잭션 | 단일 장애점(SPOF), 거대 코드베이스 빌드 지연 | 프로젝트 초기 MVP, 단일 팀(5~10명) 운영 |
| 마이크로서비스 (MSA) | 서비스별 독립 배포, 이기종 기술 스택, 무한 확장성 | 분산 트랜잭션 난제, 네트워크 지연, 인프라 비용 급증 | 여러 팀(수십 명 이상)이 독립 도메인을 운영할 때 |
| 이벤트 기반 (EDA) | 극단적 비동기 결합 해제, 뛰어난 복원력 | 최종 일관성 지연, 이벤트 순서 보장 및 추적의 난이도 | 주문/결제 파이프라인, 대규모 비동기 데이터 수집 |
| 헥사고날 / 클린 | 완벽한 단위 테스트 가능, 인프라 교체 용이성 | 많은 DTO 매핑 보일러플레이트 코드, 초기 러닝커브 | 복잡한 도메인 규칙을 가진 엔터프라이즈 시스템 |
📚 참고 문헌 및 공식 출처
| 구분 | 자료명 | 주요 내용 | 링크 |
|---|---|---|---|
| 소프트웨어 아키텍처 바이블 | Software Architecture in Practice (Bass et al.) | SEI 카네기멜론 대학교 아키텍처 드라이버, 품질속성 시나리오 | Pearson |
| 클린 아키텍처 원전 | Clean Architecture (Robert C. Martin 저) | 엉클 밥의 의존성 규칙, 포트와 어댑터 인터페이스 설계 철학 | Prentice Hall |
| 현대 분산 시스템 | Fundamentals of Software Architecture (Mark Richards & Neal Ford) | 모놀리스에서 분산 시스템까지 최신 아키텍처 스타일 및 트레이드오프 분석 | O’Reilly Media |
| ADR 표준 템플릿 | Michael Nygard’s Architecture Decision Records | 마크다운 기반의 경량 아키텍처 의사결정 기록 공식 표준 | GitHub ADR |