좋은 소프트웨어 아키텍처를 설계하는 8가지 핵심 원칙

유행하는 기술 스택의 맹목적 도입을 멈추고 시스템의 본질을 설계하라! 관심사 분리, 고응집·저결합, 의존성 역전(DIP)과 헥사고날, 계약 기반 설계, 무상태 수평확장, 서킷브레이커 장애격리, CQRS와 최종 일관성, 그리고 ADR 아키텍처 결정 기록까지 8대 핵심 원칙을 심층 해설합니다.
IT·과학
저자

Ikmyungterran

공개

2026년 7월 30일

Modified

2026년 10월 18일

0. 도구 우선(Tool-First) 사고의 함정: Redis와 Kafka부터 고르지 마라

주니어 개발자들이 시스템 설계를 시작할 때 가장 흔히 범하는 실수가 있습니다. 요구사항을 듣자마자 “어? 캐시로 Redis 붙이고, 메시지 큐로 Kafka 쓰고, 서버는 마이크로서비스(MSA)로 쪼갠 다음 쿠버네티스에 올리죠!”라고 외치는 것입니다.

이것이 바로 전형적인 ‘도구 우선(Tool-First) 사고의 함정’입니다.

소프트웨어 아키텍처는 유행하는 오픈소스 도구들을 레고 블록처럼 이어 붙이는 기술이 아닙니다. 진정한 아키텍처는 다음과 같은 냉정한 질문에 답하는 체계적인 엔지니어링 과정입니다:

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;


아키텍처의 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종:
    1. 서킷 브레이커 (Circuit Breaker): 외부 결제 API의 응답 지연이 길어지면 회로를 강제로 끊어(Open) 즉시 기본값(Fallback)을 응답함으로써, 내 서버의 모든 작업 스레드가 대기 상태로 고갈되는 연쇄 장애(Cascading Failure)를 차단합니다.
    2. 벌크헤드 (Bulkhead, 선박 격벽): 한 기능(예: 추천 시스템)의 장애가 핵심 기능(예: 결제/주문)으로 번지지 않도록 스레드 풀과 리소스를 물리적으로 격리합니다.
    3. 지수 백오프와 지터 (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대 포맷
  1. 제목 (Title): ADR-007: 실시간 알림 파이프라인으로 Redis Pub/Sub 채택
  2. 맥락 (Context): 동시 접속자 10만 명에게 지연 시간 200ms 이내로 실시간 알림을 쏴야 하는 기술적 요구 발생.
  3. 고려한 대안 (Options): 대안 A: Kafka, 대안 B: RabbitMQ, 대안 C: Redis Pub/Sub
  4. 최종 결정과 이유 (Decision): 메시지 영속성보다는 초경량 처리 속도와 기존 인프라 운영 복잡도 최소화를 위해 Redis Pub/Sub 채택.
  5. 트레이드오프 및 재검토 조건 (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

함께 읽으면 좋은 글