📖 [18장] 경계 해부학

📘 클린 아키텍처 북스터디 정리입니다

📚 도서: 로버트 C. 마틴 《Clean Architecture》
🧑‍💻 목적: 올바른 설계에 대한 감각과 습관을 익히기 위해
🗓️ 진행 기간: 2025년 7월 ~ 매주 2장

✅ 핵심 요약 (Key Takeaways)

이 장의 핵심 문장은?

시스템 아키텍처는 일련의 소프트웨어 컴포넌트와 그 컴포넌트들을 분리하는 경계에 의해 정의된다."

저자가 전달하고자 하는 메세지 요약

좋은 아키텍처는 경계를 제대로 인식하고, 설계하고, 횡단 시 의존성 전파를 방지함으로써
시스템을 유연하고 유지보수하기 쉬운 구조로 만든다.


💡 내용 정리

1. 서론

  • 시스템 아키텍처는 일련의 소프트웨어 컴포넌트와 그 컴포넌트들을 분리하는 경계에 의해 정의된다

소프트웨어 경계

  • 소프트웨어 시스템 내부의 서로 다른 책임과 관심사를 물리적 또는 논리적으로 분리하는 구조적 장치
  • 경계를 어떻게 나누느냐에 따라 빌드 전략/배포 방식/버전 관리/언어 수준의 유연성과 제약이 결정된다

2. 경계 횡단하기

  • 경계를 넘어서는 호출(예: Controller → Service)은 피할 수 없다.
  • 이때 의존성 방향이 올바르게 설정되어 있어야 경계가 무너지지 않는다.
  • 경계는 변경이 전파되는 것을 막는 방화벽을 구축하고 관리하는 역할을 해야 한다

3. 단일체와 소스 수준 분리 모드

단일체(Monolith)

  • 애플리케이션의 모든 컴포넌트가 하나의 배포 단위로 묶여 있는 형태
  • 단일체 구조에서는 컴포넌트 간 경계는 파일이나 패키지 수준(소스 코드 수준)에서만 존재
  • 컴파일도, 배포도 한 번에 이루어지기 때문에 의존성 구조가 잘못되어도 빌드 시점에 드러나지 않는다
  • 의존성 제어가 어렵다는 문제 존재

소스 수준 분리 (Source-level separation)

  • 논리적 분리만 존재하고, 물리적인 분리는 없는 수준
  • 구조적으로만 나뉘어 있을 뿐, 실질적인 독립성과 격리는 부족
  • 예: Java 프로젝트 내에서 controller, service, repository 패키지로 나눴지만, 하나의 jar로 컴파일

단일체에서 다형성과 경계 관리

동적 다형성 (Dynamic Polymorphism)
  • 동일한 인터페이스를 구현한 여러 객체 중 하나를 런타임에 선택하여 사용하는 OOP 원리
  • 고수준 컴포넌트가 구체 구현이 아닌 인터페이스에 의존하도록 구성
    → 내부 컴포넌트 간에도 경계를 인식하고 설계할 수 있음
  • 이 원리를 활용하면 단일체 안에서도 의존성을 인터페이스로 추상화하여 결합도를 낮출 수 있다
단일체에서의 다형성 활용
  • 단일체 내부에서도 컴포넌트 간 결합이 무분별해지면 경계가 무너지기 쉽다
  • 동적 다형성을 사용하면 다음과 같은 효과가 있다
    • DIP(의존성 역전 원칙) 구현
    • 전략 패턴 등 확장성 있는 코드 작성
    • 제어 흐름과 의존성 방향 분리 가능
핵심 원칙
  • 의존성은 항상 고수준 컴포넌트를 향해야 한다
  • 데이터 구조의 정의 또한 호출하는 쪽에 있어야 한다 (즉, "안쪽"에 있어야 한다)

4. 동적 링크 라이브러리와 배포 수준 분리 모드

동적 링크 라이브러리

  • .dll, .so, .jar 등 런타임에 연결되는 컴포넌트
  • 각 모듈은 별도로 빌드·배포되며, 실행 시점에 동적으로 연결됨

배포 수준 분리 (Deployment-level separation)

  • 각 모듈이 독립적으로 배포 가능
  • 서로 다른 팀이 각각의 컴포넌트를 유지/배포 가능
    • 예: UserService.dll과 OrderService.dll이 독립적으로 배포됨
  • 배포 과정에서만 차이가 날 뿐, 배포 수준의 컴포넌트는 단일체와 동일
  • 배포 단위는 나뉘어 있지만, 컴포넌트 간 통신은 여전히 함수 호출
    → 경계를 너무 자주 횡단하면 단일체처럼 결합될 위험이 있음

5. 실행 단위에 따른 경계

스레드

  • 스레드는 경계가 아니라 실행 계획의 도구
  • 여러 스레드가 하나의 경계를 공유할 수도 있고, 하나의 컴포넌트가 여러 경계를 스레드로 운영할 수도 있다

로컬 프로세스

  • OS 레벨에서 분리된 실행 단위 (예: Billing.exe, ReportGenerator.exe)
  • 각 로컬 프로세스는 정적으로 링크된 단일체이거나 동적으로 링크된 여러개의 컴포넌트로 구성될 수 있다
  • 로컬 프로세스 간 분리 전략은 단일 바이너리 컴포넌트의 경우와 동일하다
  • 아키텍처 관점의 목표: 저수준 프로세스가 고수준 프로세스의 플러그인이 되도록 만드는 것
  • 서로 다른 실행 파일이므로 통신 시 고비용(IPC, 데이터 직렬화 등)
    • OS 호출, 데이터 마샬링 및 언어 마샬링, 프로세스 간 문맥 교환 등
  • 프로세스 간 경계는 분명하지만, 통신 빈도는 최소화해야 한다

서비스

  • 가장 강력한 형태의 물리적 경계
  • HTTP/gRPC 등 네트워크 기반으로 통신 비용이 높고, 실패 가능성도 존재한다
  • 서비스 수준에서는 응답 지연을 고려할 수 있는 고수준에서 통신 해야 한다

6. 결론

경계 수준별 비교 요약

  • 독립성 및 통신 비용
구분빌드/배포 독립성의존성 전파 위험통신 비용기술 스택 분리 가능성
소스 수준 분리낮음매우 높음매우 낮음없음
동적 링크 분리중간있음낮음제한적 가능
로컬 프로세스 분리높음낮음높음가능
서비스 수준 분리매우 높음매우 낮음매우 높음완전 가능
  • 주요 특징
분류설명주요 특징
소스 수준 분리논리적 구조만 나눔. 컴파일/배포는 한 번에 진행경계는 논리적이며 컴파일/배포는 하나
동적 링크 분리런타임에 연결되는 모듈로 구성경계는 실행 시점에 느슨하게 연결됨
로컬 프로세스 분리실행 단위가 별도의 OS 프로세스서로 다른 프로세스 간 통신 (IPC), 운영체제 개입
서비스(분산 시스템) 수준 분리HTTP, gRPC 등 네트워크 기반 통신가장 물리적인 경계, 통신 비용 높음
  • 단일체를 제외한 대다수의 시스템은 한 가지 이상의 경계 전략을 사용한다
  • 대체로 한 시스템 안에서도 통신이 빈번한 로컬 경계와 지연을 중요하게 고려해야 하는 경계가 혼합되어 있다.

용어 정리

플러그인 아키텍처

정의

  • 고수준 정책(핵심 로직)이 저수준 세부사항(DB, 외부 API, 프레임워크 등)을 몰라도 동작할 수 있는 구조
    • 핵심 로직(정책)은 중심에 있고, DB, 외부 API, 프레임워크 등은 외곽에 위치
    • 인터페이스를 통해 구현체와 분리
    • 각 구현체는 고수준 로직의 플러그인이 되는 구조

0개의 댓글