코드의 의존성이 엉켜 유지보수가 힘드신가요? 클린 아키텍처(Clean Architecture)를 통해 폴더 구조만으로 시스템의 기능을 파악할 수 있는 계층형 설계 가이드를 제시합니다. 의존성 규칙부터 엔티티, 유즈케이스 계층 분리까지, 지속 가능한 소프트웨어를 위한 4가지 핵심 가이드를 지금 확인하세요.
1. 소리치는 아키텍처: 클린 아키텍처(Clean Architecture)가 지향하는 본질
소프트웨어의 폴더 구조를 처음 열었을 때 무엇이 보이시나요? 만약 Controller, Service, Repository라는 단어만 가득하다면, 그 애플리케이션이 ‘은행 앱’인지 ‘배달 앱’인지 알 길이 없습니다. 로버트 C. 마틴(Uncle Bob)이 제안한 클린아키텍처(Clean Architecture)는 아키텍처가 그 시스템의 의도(Intent)를 분명하게 드러내야 한다고 강조합니다. 이를 ‘소리치는 아키텍처(Screaming Architecture)’라고 부릅니다.
훌륭한 아키텍처는 세부 사항(프레임워크, 데이터베이스, UI)에 대한 결정을 미룰 수 있게 해줍니다. 폴더 구조는 프레임워크의 도구 상자가 아니라, 비즈니스의 도메인을 설명하는 책의 목차가 되어야 합니다. 클린아키텍처(Clean Architecture)를 도입한다는 것은 단순히 코드를 나누는 것이 아니라, 시스템의 핵심 비즈니스 로직을 외부 환경으로부터 격리하여 변화에 유연하게 대응할 수 있는 ‘영속성’을 확보하는 과정입니다.
2. 의존성 규칙: 클린 아키텍처(Clean Architecture)의 물리적 질서
클린 아키텍처(Clean Architecture)를 관통하는 단 하나의 가장 중요한 규칙은 ‘의존성 규칙(Dependency Rule)’입니다. 소스 코드 의존성은 반드시 안쪽(고수준 정책)으로만 향해야 합니다. 안쪽 계층은 바깥쪽 계층에 대해 전혀 알지 못해야 하며, 바깥쪽 계층에서 선언된 이름(함수, 클래스, 변수 등)을 안쪽 계층에서 언급해서는 안 됩니다.
이를 수학적 관계식으로 표현한다면, 시스템의 안정성($S$)은 해당 모듈이 의존하는 정도가 낮을수록 높아집니다. 특정 계층 $L$의 안정성을 $I$라고 할 때, 나가는 의존성($C_{out}$)과 들어오는 의존성($C_{in}$)의 관계는 다음과 같습니다.
$$I = frac{C_{out}}{C_{in} + C_{out}}$$
여기서 $I$가 0에 가까울수록(들어오는 의존성이 많고 나가는 의존성이 없을수록) 해당 계층은 매우 안정적인 상태가 됩니다. 클린 아키텍처(Clean Architecture)의 중심인 엔티티와 유즈케이스는 $I=0$을 목표로 설계되어야 합니다. 즉, 프레임워크나 데이터베이스가 바뀌더라도 핵심 로직은 흔들리지 않아야 한다는 뜻입니다.
3. 계층의 분리: 클린 아키텍처(Clean Architecture)를 구성하는 4가지 원(Circle)
클린 아키텍처(Clean Architecture)는 소프트웨어를 크게 4가지 계층으로 나눕니다. 이 분리는 관심사의 분리(Separation of Concerns)를 실현하는 구체적인 방법론입니다.
3.1. 엔티티(Entities)
엔티티는 전사적인 비즈니스 규칙을 캡슐화합니다. 가장 고수준의 정책이며, 외부의 변화에 영향을 받지 않는 가장 순수한 코드 영역입니다. 애플리케이션에 변화가 생기더라도 엔티티 계층은 가장 마지막까지 변하지 않아야 합니다.
3.2. 유즈케이스(Use Cases)
애플리케이션 고유의 비즈니스 규칙을 포함합니다. 엔티티를 조작하여 시스템의 자동화된 기능을 실현하며, 데이터가 엔티티로 흐르는 방식과 엔티티가 비즈니스 목표를 달성하기 위해 출력을 내보내는 방식을 제어합니다.
3.3. 인터페이스 어댑터(Interface Adapters)
유즈케이스나 엔티티에 가장 편리한 형식에서 데이터베이스나 웹 같은 외부 에이전시에게 가장 편리한 형식으로 데이터를 변환합니다. 컨트롤러, 프레젠터, 게이트웨이가 이 계층에 속합니다.
3.4. 프레임워크와 드라이버(Frameworks and Drivers)
데이터베이스, 웹 프레임워크 등 모든 세부 사항이 위치합니다. 클린 아키텍처(Clean Architecture)에서 프레임워크는 ‘도구’일 뿐이며, 비즈니스 로직을 오염시키지 않도록 가장 바깥쪽 원에 위치시켜야 합니다.
4. 테스트 용이성: 세부 사항으로부터 독립적인 클린 아키텍처(Clean Architecture)
클린 아키텍처(Clean Architecture)의 가장 큰 강점 중 하나는 압도적인 테스트 용이성입니다. 비즈니스 로직이 외부 요소와 격리되어 있기 때문에 다음과 같은 독립적인 테스트가 가능해집니다.
첫째, UI로부터 독립적입니다. 웹 사이트의 화면이나 모바일 앱의 레이아웃이 없어도 비즈니스 로직을 테스트할 수 있습니다. 시스템의 핵심 기능을 검증하기 위해 실제 브라우저를 띄울 필요가 없습니다.
둘째, 데이터베이스로부터 독립적입니다. 실제 DB 서버를 구축하지 않고도 엔티티와 유즈케이스를 테스트할 수 있습니다. Mock 객체를 사용하여 메모리 상에서 비즈니스 규칙을 검증함으로써 테스트 속도를 기하급수적으로 높일 수 있습니다.
셋째, 프레임워크로부터 독립적입니다. Spring, Express, Django와 같은 프레임워크가 제공하는 제약 사항에 얽매이지 않고 순수한 언어(Java, Kotlin, TS, Python 등)의 기능만으로 테스트 코드를 작성할 수 있습니다. 이러한 독립성은 시스템의 엔트로피가 증가하더라도 코드의 품질을 일정하게 유지하는 방어선이 됩니다.
5. 실전 가이드: 기능이 보이는 클린 아키텍처(Clean Architecture) 폴더 구조 설계
이제 이론을 넘어 실제 프로젝트에 적용할 수 있는 ‘소리치는 폴더 구조’를 설계해 보겠습니다. 핵심은 기술적인 이름(Services, Models)이 아니라 도메인(Orders, Payments, Users)을 최상위로 올리는 것입니다.
Plaintext
src/
├── domain/ (Entities)
│ ├── order/
│ │ ├── Order.ts
│ │ └── OrderItem.ts
│ └── product/
├── application/ (Use Cases)
│ ├── order/
│ │ ├── CreateOrderUseCase.ts
│ │ └── GetOrderListUseCase.ts
│ └── ports/ (Dependency Inversion용 인터페이스)
├── infrastructure/ (Frameworks & Drivers)
│ ├── database/
│ │ └── TypeOrmOrderRepository.ts
│ └── external-api/
└── presentation/ (Interface Adapters)
├── controllers/
│ └── OrderController.ts
└── dto/
이 구조의 핵심은 application/ports입니다. 유즈케이스는 실제 데이터베이스 구현체인 infrastructure를 직접 참조하지 않습니다. 대신 ports에 정의된 인터페이스를 호출하고, 실제 구현체는 런타임에 주입(Dependency Injection)받습니다. 이것이 바로 의존성 역전 원칙(DIP)을 활용하여 클린 아키텍처(Clean Architecture)를 완성하는 구체적인 기술입니다.
6. 결론: 지속 가능한 개발을 위한 클린 아키텍처(Clean Architecture)의 선택
결론적으로 클린 아키텍처(Clean Architecture)는 단순히 “코드를 어디에 둘 것인가”에 대한 답이 아닙니다. 소프트웨어의 수명이 다할 때까지 변경의 고통을 최소화하고, 기술적 부채가 쌓이는 속도를 늦추기 위한 고도의 전략입니다.
처음에는 계층을 나누고 인터페이스를 작성하는 과정이 번거롭고 과하게 느껴질 수 있습니다. 하지만 시스템이 커지고 프레임워크의 버전이 바뀌며, 데이터베이스를 교체해야 하는 순간이 오면 클린 아키텍처(Clean Architecture)가 제공하는 격벽의 가치는 수조 원의 유지보수 비용 절감으로 돌아옵니다.
폴더 구조만 봐도 시스템의 목적이 소리치는 설계를 지향하십시오. 기술은 변하지만 비즈니스의 본질은 변하지 않습니다. 그 본질을 가장 안전한 곳에 보호하는 것, 그것이 바로 클린 아키텍처(Clean Architecture)가 우리에게 주는 최종적인 교훈입니다.