Clean Architecture 정리 – 5부 17장 아키텍처에서 경계 나누기
2025. 7. 23. 14:28

좋은 소프트웨어 아키텍처란, 경계를 설정하는 기술이다.

경계는 요소(컴포넌트, 객체)를 분리하고, 불필요한 조기 결정을 늦춤으로써 핵심 업무 로직을 오염시키지 않도록 하는 데 목적이 있다.

 

이른 결정을 피해야 하는 이유는, 그러한 결정이 시스템의 유지 효율성을 급격히 떨어뜨리는 결합도를 야기하기 때문이다.

특히 업무 요구사항과 무관한 결정(예: 프레임워크, DB, DI 프레임, 라이브러리 등)은 가능한 한 늦게 내리는 것이 바람직하다.

 

좋은 아키텍처는 이러한 조기 결정을 유예할 수 있게 설계되며, 설령 나중에 결정하더라도 시스템 전체에 영향을 주지 않도록 한다.


📌 사례 1: 너무 이른 결정이 만든 과도한 복잡성

사내에서 사용하는 단순한 휴가 신청 시스템을 만들기로 했다.

요구사항은 간단했다. 직원이 휴가를 신청하고, 팀장이 승인하면 끝이었다.

 

👉 그러나 개발팀은 너무 앞서 나갔다

  • '나중에 외부 고객도 쓸 수 있지 않을까?'라는 생각에 로그인 시스템을 OAuth 기반으로 설계
  • '나중에 팀 구조가 복잡해질 수도 있어'라는 이유로 부서 계층을 트리 구조로 구현

 

👉 결과적으로 생긴 문제

  • OAuth를 도입하면서 토큰 발급, 리프레시 처리, 외부 인증 연동 등 불필요한 복잡성이 생겼고, 로그인 문제 발생 시 디버깅 난이도도 상승함
    (사실 내부 직원 20명만 쓰는 서비스였고, 사내 계정 기반 단순 로그인으로 충분했음)
  • 부서 구조를 트리로 구성하면서 팀장 탐색 로직이 복잡해짐
    (반면 단일 테이블 구조였다면 '팀장 ID' 컬럼만 추가해 SELECT * FROM users WHERE id = 팀장_ID 쿼리 한 줄이면 됐음)

 

📌 사례 2: 늦춘 결정이 만든 유연한 시스템

한 팀이 메모 관리 앱을 만들고 있다.

이 앱은 메모 작성 및 태그 검색 기능만 필요했다.

 

👉 그래서 다음과 같은 방식으로 설계했다:

  • 저장은 처음에는 필요하지 않으므로, 인메모리 저장소(InMemoryMemoRepository) 로 시작
  • 저장소 인터페이스는 정의만 해두고, 더미 구현으로 대체
  • 어떤 DB를 쓸지는 나중에 결정 → JSON 파일을 사용

 

👉 얻은 유연성

  • 초기 개발에 집중 → 저장 로직 없이 프론트 동작만 빠르게 반복 가능
  • 저장이 필요해졌을 때 JSON 또는 MySQL로 자연스럽게 확장 가능
  • DB 미사용으로 로컬 테스트가 쉬움 (트랜잭션, 스키마 설계, 접속 설정 불필요)

 

🔧 어떻게 어떤 데이터베이스든 나중에 붙일 수 있을까?

 

1. 저장소 인터페이스 정의

interface MemoRepository {
  findById(id: string): Promise<Memo>
  save(memo: Memo): Promise<void>
  delete(id: string): Promise<void>
}

 

2. 인메모리 저장소 스텁 구현

class InMemoryMemoRepository implements MemoRepository {
  private data = new Map<string, Memo>()
  ...
}

 

3. 나중에 다른 구현체로 교체 가능

class JsonMemoRepository implements MemoRepository {...}
class MySQLMemoRepository implements MemoRepository {...}

 

4. 앱은 구현체에 의존하지 않도록 구성

class MemoService {
  constructor(private repo: MemoRepository) {...}
}

✒️ 언제 어디에 선을 그어야 하는가?

관련 없는 것들 사이에 선을 그어야 한다.

  • GUI ↔ Business Rules → 분리 필요
  • DB ↔ GUI → 분리 필요
  • DB ↔ Business Rules → 분리 필요

 

💿 업무 규칙과 DB는 분리되어야 한다

업무 규칙은 DB가 어떻게 동작하는지 몰라도 된다.

단지 “데이터를 저장하고 불러올 수 있다”는 추상적 계약만 있으면 된다.

이 추상화는 인터페이스로 구현 가능하며, 실제 구현은 감출 수 있다.

Business Rules
    ↓
Database Interface
    ↑
DB Controller → DB

 

이 구조에서는 Database Controller를 의존하는 객체가 없기 때문에, 해당 지점을 경계선으로 삼을 수 있다.

이렇게 경계를 나누면 다음과 같은 일이 가능하다:

  • Business Rules는 DB 구현체를 몰라도 된다 → DB 종류나 구조가 바뀌어도 영향 없음
  • 테스트할 때는 DB 없이도 가능 → InMemory 저장소만으로 단위 테스트 가능
  • 유지보수와 교체가 쉬움 → DB Controller만 바꾸면 다른 저장소로 쉽게 전환

 

 

👁️ GUI도 마찬가지로 분리되어야 한다

Business Rules
    ↑
   GUI
  • GUI는 표현 방식일 뿐이며, 변경이 자주 일어난다
  • Business Rules는 GUI에 대해 아무것도 알 필요 없다
  • 덕분에 CLI, 모바일, 웹 등 다양한 UI로 교체 가능

🔌 플러그인 아키텍처로 일반화하기

GUI와 DB는 Business Rules에 의존하는 방향으로 연결된다.

이 구조는 곧 플러그인 아키텍처다.

  • Business Rules는 Core (변경에 강함)
  • GUI / DB는 Plugin (변경이 잦음)

플러그인 구조는 다음과 같은 장점이 있다:

  • UI 변경에 영향을 받지 않음
  • DB 교체에 유연하게 대응 가능
  • 업무 규칙은 독립적으로 테스트 가능

이러한 구조는 같은 이유로의 변경을 기준으로 컴포넌트를 나누는 것이며, 단일 책임 원칙(SRP) 과도 일맥상통한다.


🧩 마무리 정리

소프트웨어 아키텍처에서 경계를 설정하려면 먼저 시스템을 컴포넌트 단위로 나눠야 한다.

  • 핵심 컴포넌트: 비즈니스 로직 중심 (Business Rules)
  • 플러그인 컴포넌트: 표현(GUI), 저장소(DB)

의존성은 항상 핵심을 향해야 하며, 인터페이스를 기준으로 방향이 바깥에서 안으로 흐르도록 설계되어야 한다.

 

이는 다음 두 가지 원칙을 따른다:

  1. DIP (의존성 역전 원칙)
  2. SAP (안정된 추상화 원칙)

이러한 설계는 시스템을 더 유연하고 견고하게 만든다.

변경이 자주 발생하는 컴포넌트가 시스템의 중심을 침범하지 않도록, 우리는 경계선을 명확히 그어야 한다.

 

 

※ 본 글은 『Clean Architecture』(로버트 C. 마틴 저) 5부 17장을 기반으로 학습 목적으로 요약한 글입니다.

※ 이 글은 책의 내용을 요약한 것으로, 원문 없이 읽을 경우 오해의 여지가 있을 수 있습니다. 정확한 이해를 위해 원서의 정독을 권장합니다.