1. Clean Architecture란 무엇인가?
1-1. 정의와 철학
Clean Architecture는 2012년 Robert C. Martin(‘Uncle Bob’)이 공식적으로 소개한 소프트웨어 설계 아키텍처다. 핵심 개념은 시스템의 핵심 비즈니스 규칙을 외부로부터 완전히 독립시켜, 변화에 유연하면서도 테스트 가능한 구조를 만드는 데 있다. 그는 레이어드 아키텍처나 헥사고날 아키텍처 등 기존 구조의 장점들을 통합해 더 정제된 구조를 제시했으며, Clean Architecture는 클린 코드의 연장선으로서 “변화에 강한 소프트웨어”를 위한 철학으로 자리 잡았다.
이 아키텍처는 단순히 코드를 정리하는 기술이 아니라, 소프트웨어 시스템의 생존력, 확장성, 그리고 협업 구조를 어떻게 설계할 것인가에 대한 전략이다. 핵심은 도메인을 모든 의존성에서 해방시켜 비즈니스 정책만을 표현하는 중심 구조를 만든다는 것이다.
1-2. 방사형 계층 구조
Clean Architecture는 원형 구조로 설계되며, 중심에는 도메인 엔티티, 그 바깥에는 유스케이스(Application Services), 그 외곽에는 인터페이스 어댑터와 프레임워크가 위치한다. 이 구조의 핵심은 의존성의 방향이다. 항상 바깥에서 안쪽으로만 의존이 발생하며, 안쪽의 코드(Entity)는 바깥의 어떤 구현도 모른다. 이런 구조를 통해서 어떤 계층도 변경되더라도 안쪽의 도메인은 영향받지 않으며, 기술 스택 교체, UI 변경, DB 구조 변경 등의 파급효과가 억제된다. 즉, 구조적 유연성과 견고함을 동시에 확보하는 구조다.
1-3. 프레임워크 독립성과 기술 선택 자유
Clean Architecture는 기술에 종속되지 않는 구조를 지향한다. 프레임워크(Spring, Django, Express 등)는 도구에 불과하며, 도메인 로직과 직접 연결되어선 안 된다. 예를 들어 Spring의 @Service, @Repository, @RestController 같은 어노테이션이 핵심 도메인 코드에 직접 붙어 있다면 이는 Clean Architecture 관점에서는 오염된 구조로 간주된다. 이러한 분리를 통해, 프로젝트 중반에 프레임워크를 다른 것으로 교체하거나, DB를 MySQL에서 MongoDB로 변경해도 도메인과 유스케이스는 그대로 유지될 수 있다. 이로써 시스템은 **기술 변화에 대한 내성(Resilience)**을 확보하게 된다.
1-4. 왜 Clean Architecture인가?
현실의 프로젝트는 시간이 지날수록 비즈니스 로직이 컨트롤러와 서비스에 뒤엉켜 ‘스파게티 구조’가 되기 쉽다. 이 구조는 초기에 빠른 개발이 가능하나, 시간이 지날수록 버그가 늘고 유지보수가 어렵고, 새 개발자가 이해하기 힘든 코드베이스가 된다. Clean Architecture는 이런 문제를 사전에 예방한다. 각 계층의 책임을 분리하고, 변경의 파급범위를 최소화함으로써 대규모 유지보수, 팀 간 협업, 테스트 자동화, CI/CD 통합 등 실제 운영 단계에서 강한 구조를 제공한다.
2. 아키텍처 구조의 계층별 구성
2-1. Entity 계층 (Enterprise Business Rules)
엔티티 계층은 애플리케이션의 가장 핵심적인 비즈니스 규칙이 위치하는 곳이다. 여기에는 객체 모델, 값 객체(Value Object), 정책, 계산식, 상태 변이 로직 등이 포함되며, 가장 순수한 형태의 자바 또는 코틀린 객체로 구현된다. 이 계층은 어떤 기술에도 의존하지 않으며, 테스트가 용이하고, 설계 원칙만으로도 구조를 이해할 수 있어야 한다.
실무 예시로는 "회원의 나이는 19세 이상이어야 한다", "총 결제 금액이 10만 원 이상이면 할인 대상이 된다" 같은 규칙이 여기에 구현된다. 이 계층은 시스템의 ‘철학’을 담고 있기 때문에 가능한 한 단순하면서 명확해야 하며, 오랫동안 유지될 수 있어야 한다.
2-2. Use Cases 계층 (Application Business Rules)
Use Cases 계층은 시스템이 제공하는 기능적 시나리오를 담당한다. 즉, “주문 생성”, “회원 가입”, “포인트 차감” 등의 비즈니스 플로우가 여기서 구현된다. 유스케이스는 Entity 계층의 기능을 조합하고 조율하는 역할을 하며, 상태 저장, 로깅, 트랜잭션 관리 등을 포함할 수 있다.
중요한 점은 이 계층도 외부 기술에 의존하지 않는다는 것이다. 예를 들어, 결제를 담당하는 유스케이스는 “결제하기”라는 명확한 로직을 중심으로 구성되며, 외부 결제 API는 인터페이스로 추상화하여 호출한다. 이로 인해 로직 중심의 흐름이 명확하게 분리된다.
2-3. Interface Adapters 계층
이 계층은 시스템과 외부 환경의 데이터 전달 및 형식 변환을 담당한다. REST API Controller, View Renderer, GraphQL Resolver, CLI 명령어 처리기 등이 여기에 해당하며, 주로 유스케이스를 호출하는 역할을 한다. 또한 어댑터는 DTO ↔ Domain Model 변환, 입력값 유효성 검증, 출력 포맷팅 등 사용자와 시스템 간 연결고리로 작동한다. 예를 들어, UserController는 CreateUserUseCase를 호출하고, 결과를 API 응답으로 변환하여 반환하는 역할을 한다. 이 계층은 바뀌기 쉬운 외부 요구사항을 내부 구조와 단절시키는 방패 역할을 수행한다.
2-4. Frameworks & Drivers 계층
가장 바깥 계층에는 기술 요소가 집중된다. DB, 프레임워크(Spring, Django), UI 라이브러리(Vue, React), 메시지 브로커(Kafka), 인증 시스템 등 다양한 인프라와 기술 라이브러리가 여기에 속한다. 이 계층은 가장 자주 바뀌는 영역이므로, 도메인이나 유스케이스와 연결되어 있어서는 안 된다. 이 계층은 바깥에서 포트를 통해 어댑터를 호출하며, 시스템의 실행을 책임지지만 핵심 로직은 전혀 포함하지 않아야 한다. 이 구조를 통해 기술은 애플리케이션의 구현 디테일로 남고, 시스템은 기술 변화에 휘둘리지 않는다.
3. 의존성 규칙 (Dependency Rule)
3-1. 규칙의 핵심 개념
Clean Architecture에서 가장 중요한 규칙 중 하나는 “의존성은 반드시 안쪽으로 향해야 한다”는 것이다. 즉, 외부 계층(Controller, Adapter, Repository 등)은 내부 계층(Use Case, Entity)을 참조할 수 있지만, 반대로는 절대 불가능하다. 이렇게 하면 핵심 로직이 어떤 기술이나 외부 시스템에도 의존하지 않고 독립적으로 존재할 수 있다.
3-2. DIP(Dependency Inversion Principle)의 실전 구현
이를 실현하기 위해 DIP를 활용한다. 유스케이스는 외부 시스템과 연결되지 않고, 인터페이스(Port)를 통해 호출하거나 호출되도록 설계한다. 예를 들어, UserRepositoryPort라는 인터페이스를 정의하고, 그 구현은 외부 어댑터(JpaUserRepositoryAdapter)가 담당한다. 실제 코드에서는 이러한 포트를 생성자나 파라미터를 통해 주입(DI)하게 되며, Spring에서는 @Component, @Autowired 대신 명시적인 @Configuration + @Bean 조합으로 바인딩하는 것이 구조적 명확성을 확보하는 데 유리하다.
3-3. 입출력 흐름 분리
의존성 규칙은 단순한 객체 참조만의 문제가 아니다. Clean Architecture는 **입력 흐름(웹 요청, CLI 호출)**과 **출력 흐름(DB 저장, 메일 전송)**을 명확히 분리한다. 입력은 Input Port를 통해 유스케이스를 호출하고, 출력은 Output Port를 통해 외부에 결과를 전달한다. 이처럼 흐름까지 구조화되면 디버깅, 테스트, 확장이 모두 쉬워진다.
3-4. 테스트 관점에서의 의존성 분리 효과
이 규칙은 테스트 전략에도 큰 영향을 준다. UseCase는 Output Port를 주입받기 때문에 외부 시스템 없이도 동작 가능하다. 이를 통해 단위 테스트 시 실제 DB, 메시지 큐, 외부 API가 없어도 로직 검증이 가능해지며, 테스트 속도와 신뢰도가 크게 향상된다. 또한 Integration 테스트에서는 실제 어댑터를 교체해 전체 흐름을 검증하고, 시스템 테스트와 QA에서도 구조별 테스트가 가능하다. 이런 구조적 테스트 분리는 CI/CD 자동화 파이프라인 구성에도 매우 유리하다.
4. SOLID 원칙과의 연결
4-1. SRP (단일 책임 원칙): 계층의 책임 명확화
Clean Architecture는 구조적으로 SRP(Single Responsibility Principle)를 구현한다. 각 계층은 오직 하나의 관심사에만 집중하도록 강제된다.
- Entity 계층: 비즈니스 규칙만을 다룬다. 예: 사용자의 나이 계산, 결제 금액 검증 등
- UseCase 계층: 특정 시나리오 절차만 정의한다. 예: “회원 가입 시 포인트 지급”
- Interface Adapter 계층: 입력과 출력을 다룬다. 예: HTTP 요청 처리, DTO 변환 등
이처럼 계층마다 책임이 분리되면, 로직이 얽히지 않고 관심사가 명확하게 분리된다. 예를 들어, 할인 정책이 변경될 경우, 컨트롤러나 리포지토리 코드가 아니라 Entity 또는 Domain Service만 수정하면 된다. 이를 통해 리팩토링 시 오류 확산을 방지하고, 이해 가능한 코드베이스를 유지할 수 있다.
4-2. OCP (개방-폐쇄 원칙): 확장에 열려 있고 수정에 닫혀 있도록
Clean Architecture는 새로운 요구사항에 유연하게 대응하면서도 기존 코드를 안정적으로 유지할 수 있도록 한다. 예를 들어, 새로운 결제 방식(카카오페이, 애플페이 등)을 도입한다고 하자.
- 기존의 PaymentProcessorPort를 수정하지 않고,
- 새로운 구현체인 KakaoPayProcessorAdapter, ApplePayProcessorAdapter를 추가하면 된다.
이는 OCP(Open-Closed Principle)를 구조적으로 보장하는 방식이다. 기존의 핵심 유스케이스와 도메인 코드는 변경하지 않고, 새로운 기능은 새로운 클래스나 어댑터로 확장하여 도입할 수 있다. 결국 Clean Architecture는 코드 재사용성과 기능 확장성을 동시에 충족시키는 구조로 작동하게 된다.
4-3. DIP (의존성 역전 원칙): 정책이 구현을 지배한다
Clean Architecture의 중심 철학은 바로 DIP(Dependency Inversion Principle)에 있다. 고수준 정책(유스케이스, 엔티티)은 저수준 구현(프레임워크, DB, 외부 API)보다 우선하며, 구현이 정책을 따르게 설계되어야 한다.
예시:
- 유스케이스는 UserRepositoryPort만 알며,
- 실제 구현은 JPA, MongoDB, REST API 등 어떤 것이든 외부 어댑터에서 처리한다.
이를 통해 유스케이스는 “어떻게 저장할 것인가”가 아니라 “저장한다”는 추상화에만 집중할 수 있다. 이런 구조는 단위 테스트, 기술 교체, 유지보수 모든 면에서 유리하며, 시스템을 정책 중심의 견고한 구조로 전환시킨다.
4-4. ISP와 LSP의 실질적 구현
Clean Architecture는 또한 ISP(Interface Segregation Principle)와 LSP(Liskov Substitution Principle)도 구조적으로 실현한다.
- ISP: 포트 인터페이스는 단일 목적만 가진다. 예: SendMailPort, LoadUserPort, SaveOrderPort처럼 기능별로 작게 쪼개진다. 이는 거대한 ‘all-in-one 인터페이스’가 아닌, 작고 명확한 책임 분할을 의미한다.
- LSP: 포트를 구현하는 어댑터는 언제든지 대체 가능해야 한다. 예: SmtpMailSenderAdapter를 테스트 환경에서는 MockMailSender로 교체해도 시스템이 문제 없이 동작해야 한다.
이 두 원칙이 적용되면, 어댑터의 교체, 기능 변경, 테스트 대역 삽입이 매우 자연스럽고 안정적으로 이루어질 수 있다. 결국 Clean Architecture는 구조적으로 SOLID를 내재화한 시스템이며, 아키텍처 전체가 원칙 중심으로 동작하도록 만든다.
5. 아키텍처 도입의 이점
5-1. 기술 독립성 확보
Clean Architecture의 가장 실질적인 장점 중 하나는 기술 독립성이다. 도메인과 유스케이스는 어떤 프레임워크나 외부 기술에도 의존하지 않도록 설계되기 때문에, 시스템의 핵심 로직을 손상시키지 않고도 기술 스택을 유연하게 변경할 수 있다.
예를 들어 다음과 같은 기술 교체가 가능해진다:
- RDBMS(MySQL) → NoSQL(MongoDB)
- Spring MVC → NestJS
- REST API → GraphQL
- 내부 DB → 외부 API 연동
이런 기술 교체는 바깥 계층(어댑터 및 프레임워크)에서만 처리되기 때문에, 유스케이스와 도메인은 그 어떤 코드도 수정하지 않고도 계속 동작한다. 이는 실무에서 유지보수성, 시스템 확장성, 리팩토링 시 안전성을 크게 향상시키는 요인이 된다. 특히 스타트업이나 빠르게 변화하는 비즈니스 환경에서는 기술 방향을 유연하게 전환할 수 있는 기반이 된다는 점에서 매우 강력한 이점이다.
5-2. 유지보수성과 변화 대응력 향상
시간이 흐를수록 시스템은 더 많은 기능과 복잡성을 갖게 되며, 요구사항 변경이 빈번해진다. Clean Architecture는 이러한 변화에 구조적으로 강하다. 각 계층의 책임이 명확하게 분리되어 있기 때문에, 특정 기능의 변경이 다른 계층에 미치는 영향을 최소화할 수 있다.
예를 들어, 결제 정책 변경이나 할인 로직 추가처럼 비즈니스 규칙이 바뀌는 경우:
- 도메인 계층의 정책 객체만 수정하면 된다
- 유스케이스나 컨트롤러는 수정하지 않아도 된다
이러한 구조는 코드 충돌을 줄이고, 버그 전파를 방지하며, 긴밀하게 연결된 코드들 사이에서 의도치 않은 사이드 이펙트를 최소화해준다. 또한 주기적인 리팩토링이나 코드 품질 개선을 하더라도 전체 시스템에 영향을 미치지 않고 부분적 개선이 가능하다는 점에서 유지보수 측면에서 매우 유리하다.
5-3. 테스트 용이성과 자동화 환경에의 적합성
Clean Architecture는 테스트가 쉬운 구조를 만들기 위한 목적도 강하게 반영되어 있다. 특히 도메인과 유스케이스는 외부 시스템이나 기술과 완전히 분리되어 있기 때문에, 다음과 같은 테스트 환경을 구현할 수 있다:
- 도메인 객체는 순수 단위 테스트(Unit Test)로 검증 가능
- 유스케이스는 Output Port를 Mock 처리하여 테스트 가능
- 어댑터는 통합 테스트(Integration Test) 또는 E2E 테스트 대상
이는 테스트 커버리지 확보와 자동화된 검증 루틴을 구성하는 데 매우 효과적이다. CI/CD 파이프라인에서도 각 계층별로 독립적인 테스트가 가능하기 때문에, 릴리즈 안정성과 테스트 속도를 크게 높일 수 있다. 또한 테스트 시 어떤 계층이 실패했는지를 명확히 구분할 수 있어 디버깅 효율도 극대화된다.
5-4. 조직 협업 및 역할 분담 최적화
Clean Architecture는 명확한 구조를 제공하기 때문에, 팀 간 협업 구조도 자연스럽게 분리되고 최적화된다. 개발자, 디자이너, 아키텍트, 테스트 엔지니어 등 다양한 직무가 명확한 책임 범위 내에서 작업할 수 있다.
예를 들어,
- 프론트엔드 개발자는 Controller와 요청/응답 포맷만 신경 쓰면 되고,
- 백엔드 유스케이스 개발자는 입력을 받아 로직을 조정하는 데 집중하며,
- 도메인 설계자는 비즈니스 규칙을 모델링하는 데 전념할 수 있다.
이는 프로젝트 규모가 커질수록 중요해지며, 코드 리뷰 기준, 모듈 분리, 인터페이스 계약 작성 등 모든 협업 요소에서 기준점을 제공한다. 특히 기능 단위로 업무를 분할하거나, 신규 인원이 투입될 경우에도 특정 계층만 이해하면 되므로 온보딩 속도도 빨라지고 생산성이 향상된다.
6. 도입 시 고려사항과 단점
6-1. 구조적 복잡성과 오버엔지니어링의 위험
Clean Architecture는 그 자체로는 이상적인 구조지만, 모든 프로젝트에 무조건 적합하지는 않다. 특히 간단한 CRUD 중심의 웹 시스템이나 단기 이벤트 시스템, 프로토타입 수준의 MVP 제품에서는 오히려 불필요하게 복잡한 구조가 될 수 있다.
예를 들어, 단순한 게시글 조회 기능을 위해:
- UseCase 클래스 생성
- Input Port 인터페이스 작성
- 어댑터 클래스 작성
- DTO ↔ Entity 변환 로직 작성
이라는 과정을 거치게 되면 기능보다 구조 설계에 드는 비용이 훨씬 커지는 문제가 발생할 수 있다. 이런 구조적 과잉은 유지보수가 어려운 팀에는 오히려 리스크로 작용할 수 있다. 따라서 도입 전 반드시 프로젝트의 특성과 범위를 고려해 구조 적용 범위를 결정해야 한다.
6-2. 초기 설계 및 팀 내 정렬 비용 증가
Clean Architecture는 도입 시 반드시 팀원 간 설계 철학, 코드 구성 기준, 책임 범위에 대한 합의가 선행되어야 한다. 이는 다음과 같은 초기 설계 비용으로 이어진다:
- 폴더 구조 및 네이밍 규칙 수립
- 계층별 책임 정의서 작성
- 포트/어댑터 분리 기준 정립
- 공통 유스케이스 패턴 정의
만약 이러한 기준 없이 각자 해석에 따라 구조를 구성하게 되면, Clean Architecture의 장점은 사라지고 **‘겉모습만 복잡한 구조’**가 되어버릴 수 있다. 특히 신입 개발자나 경력자 간의 설계 이해도 차이가 클 경우, 같은 구조 내에서도 코드의 성향이 분산되어 일관성이 크게 떨어지게 된다.
6-3. 높은 학습 곡선과 도입 장벽
Clean Architecture는 전통적인 MVC나 레이어드 아키텍처에 익숙한 개발자에게는 비교적 낯선 철학일 수 있다. 단순히 컨트롤러-서비스-DAO로 구성된 구조에 비해 계층이 더 많고, 역할이 더 명확히 분리되어 있으므로 학습 장벽이 존재한다.
특히 다음과 같은 개념들이 어렵게 느껴질 수 있다:
- 유스케이스 기반 개발 방식
- Input/Output Port의 인터페이스 설계
- 의존성 역전(DIP)의 실제 구현 방식
- 테스트 관점에서의 계층 분리
따라서 도입 초기에 사내 기술 세미나, 아키텍처 가이드 문서, 코드 리뷰 가이드 등을 통한 학습 장치가 반드시 필요하며, 그렇지 않으면 실제 도입 이후에도 팀원 간 구조 이해도 차이로 인해 협업이 어려워질 수 있다.
6-4. 일관성 유지와 구조 오염 방지의 어려움
Clean Architecture는 이상적인 구조인 만큼, 그 구조를 얼마나 일관성 있게 유지하느냐가 핵심 성공 요소다. 하지만 프로젝트가 커지고 팀원이 늘어나면서 다음과 같은 문제가 자주 발생한다:
- 유스케이스를 어디까지 분리할 것인가에 대한 판단 불일치
- 동일한 포트를 중복 정의하거나, 과도하게 큰 인터페이스 설계
- 서비스 계층에 로직이 다시 집중되는 현상(Service God Class)
- 어댑터에서 도메인 로직을 포함하는 구조 오염
이러한 문제는 결국 구조 자체의 순수성을 훼손하게 되며, 원칙 없는 Clean Architecture는 오히려 전통적인 스파게티 코드보다 더 위험한 구조가 될 수 있다. 따라서 구조를 도입할 때는 반드시 다음이 동반되어야 한다:
- 팀 내 아키텍처 설계자 또는 가디언 역할 설정
- 포트/유스케이스/엔티티 네이밍 기준과 문서 정리
- 코드 리뷰 시 계층 간 침범 여부 체크 항목 포함
- 정적 분석 도구 활용(SonarQube 등)으로 의존 방향 자동 점검
7. 레이어드 아키텍처와의 비교
7-1. 호출 방향과 의존성의 구조적 차이
레이어드 아키텍처는 일반적으로 위에서 아래 방향으로 흐르는 수직적 구조를 가진다. 예를 들어 Controller → Service → Repository → DB 순으로 호출되며, 각 계층은 하위 계층을 직접 호출하고 의존한다. 이 구조는 개발 초기나 단순한 애플리케이션에서는 매우 직관적이며 빠른 구현이 가능하다는 장점이 있다.
그러나 이런 구조에서는 기술적 세부사항(예: ORM, DB 쿼리, API 호출 등)이 도메인 로직과 결합되기 쉬워진다. 도메인이 하위 기술 계층에 의존하게 되면서 구조적으로 강한 결합이 발생하고, 그로 인해 비즈니스 정책이 기술 변경의 영향을 받는 구조적 취약성이 생긴다.
반면 Clean Architecture는 의존성의 방향을 반대로 설계한다. 외부 계층이 안쪽 계층에 의존하도록 설계되며, 도메인은 어떤 프레임워크나 외부 구현체도 모른다. 이로 인해 도메인은 기술의 변화, DB의 종류, API 포맷 변화 등으로부터 독립성을 갖게 되며, 구조적으로 오염되지 않는다.
7-2. 도메인 오염 여부와 비즈니스 규칙의 위치
레이어드 아키텍처에서는 서비스 계층에 모든 로직이 몰리는 경향이 있다. 예를 들어, 할인 계산, 배송 정책, 포인트 적립 등 다양한 비즈니스 로직이 Controller나 Service 안에서 혼재되어 작성되기 쉽다. 이런 구조는 시간이 지날수록 서비스 클래스가 거대해지고, 수정 시 예기치 않은 사이드 이펙트가 발생할 수 있다. 이로 인해 도메인은 결국 DTO에 가까운 형태로 전락하게 된다.
Clean Architecture는 각 로직의 위치를 명확히 구분한다. 도메인 객체는 규칙과 계산을, 유스케이스는 흐름 제어를, 어댑터는 외부와의 연결을 담당한다. 이러한 책임 분리로 인해 도메인은 항상 기술로부터 독립적으로 유지되며, 시스템이 성장하더라도 비즈니스 정책이 명확히 구조화된다.
예를 들어, 회원 등급에 따라 배송비를 다르게 계산하는 정책이 바뀌더라도 해당 로직은 Entity나 도메인 서비스에서만 수정하면 되며, 이 변경은 컨트롤러나 API 응답에 영향을 주지 않는다.
7-3. 테스트 전략 및 품질 확보 차이
레이어드 아키텍처는 구조적으로 계층 간 의존성이 강하게 연결되어 있어 단위 테스트를 구성하기 어렵다. 대부분의 로직이 서비스 계층에 집중되고, 그 내부에서 DB, 외부 API, 트랜잭션 등이 복합적으로 작동하기 때문에 결국 전체 흐름을 통합 테스트로 검증하게 된다. 이는 테스트 속도와 정밀도 모두를 저하시킨다.
Clean Architecture는 각 계층이 테스트 단위로 분리되도록 설계되어 있다. 유스케이스는 Output Port를 통해 외부 의존을 추상화하고, 테스트 시 Mock으로 대체할 수 있다. 도메인 로직은 순수 객체로 작성되어 테스트가 단순하며 빠르다. 결과적으로 Clean 구조는 테스트 자동화, TDD, CI/CD 통합 환경에서 훨씬 유리한 기반을 제공한다. 또한 구조적으로 Input Port와 Output Port가 분리되어 있기 때문에, 유닛 테스트, 인수 테스트, 계약 테스트(Contract Test) 등 다양한 테스트 전략을 적용하기에 유연하다.
7-4. 적용 대상과 현실적인 선택 기준
레이어드 아키텍처는 빠른 구현이 필요한 프로젝트, 기술 복잡도가 낮은 서비스, 단기 운영이 예정된 시스템에 적합하다. 예를 들어 단순 조회 API, 관리자 툴, 임시 캠페인 페이지, 프로토타입 등에는 Layered 구조가 더 효율적일 수 있다. 개발자가 이미 익숙하고, 팀원 간 합의가 필요 없으며, 프레임워크가 기본 템플릿을 제공하기 때문이다.
반면 Clean Architecture는 도메인이 복잡하고, 기능이 지속적으로 확장되며, 여러 팀이 동시에 협업하거나 장기적인 운영이 예상되는 프로젝트에 매우 적합하다. 예를 들어 커머스, 물류, 결제, 인프라, 보안 등 도메인 중심 사고와 구조화된 로직이 필요한 프로젝트에서는 Clean 구조가 필수적인 설계 전략이 될 수 있다. 또한 Clean 구조는 마이크로서비스 전환, SaaS 구조 확장, API 공개 플랫폼 설계와 같은 장기 기술 전략과도 궁합이 좋다. 초기 비용은 크지만, 중장기적 ROI(투자 대비 수익)는 오히려 높다.
8. 실무 적용 전략
8-1. 작은 단위부터 점진적으로 시작하기
Clean Architecture는 그 구조 자체가 복잡하기 때문에, 처음부터 전 기능에 일괄 적용하는 방식은 대부분 실패로 이어진다. 따라서 현실적인 전략은 핵심 도메인부터 점진적으로 구조화해가는 것이다.
예를 들어, 전체 시스템 중 변화가 잦고 테스트가 중요한 영역(예: 결제, 주문, 인증 등)을 먼저 선택한다. 해당 기능을 다음 단계로 분리한다:
- 컨트롤러에서 UseCase를 호출하도록 분리
- UseCase 내부에서 도메인 객체만 호출하도록 리팩토링
- 외부 의존은 Output Port로 추상화
이렇게 순차적으로 정리해나가면, 기존 코드를 완전히 뜯어고치지 않고도 점진적인 아키텍처 전환이 가능하다. 이 과정에서 새로운 기능은 Clean 구조로 작성하고, 기존 구조는 리팩토링 시 함께 구조화를 시도하는 것이 좋다.
8-2. 아키텍처 템플릿과 프로젝트 스캐폴딩 도입
팀원 간 구조 해석이 다르거나, 초기 코드 작성 시 규칙이 지켜지지 않는 경우 Clean Architecture는 쉽게 무너진다. 이를 방지하기 위해 아키텍처 템플릿(프로젝트 스캐폴딩)을 도입하는 것이 매우 효과적이다.
예를 들어 다음과 같은 구성 기준을 템플릿으로 고정할 수 있다:
- domain, application, adapter.in, adapter.out 디렉토리 구조
- 포트 인터페이스 파일명 접미사 (UseCase, Port)
- 유스케이스 별 책임 및 테스트 작성 규칙
- DTO ↔ Entity 매핑 위치와 방식
이렇게 정형화된 템플릿을 팀 전체에 배포하고, 신규 기능 개발 시 이를 기반으로 시작하게 하면 구조 일관성, 코드 가독성, 테스트 전략까지 통일할 수 있다. NestJS, Spring Boot, .NET 등 프레임워크 별로 템플릿을 사전에 만들어두는 것도 좋은 전략이다.
8-3. 팀 내 설계 기준과 커뮤니케이션 체계 수립
Clean Architecture의 핵심은 팀 전체가 구조적 철학을 공유하고, 같은 기준으로 구조를 유지하는 것이다. 이를 위해 문서화된 설계 기준과 의사소통 체계가 반드시 필요하다.
다음 요소들을 사전에 정의하고 공유하는 것이 좋다:
- 유스케이스 분리 기준: 언제 UseCase를 나누는가? 어떤 로직까지 포함해야 하는가?
- 포트 네이밍 규칙: RegisterUserUseCase, SendMailPort 등 의미 중심 명명
- 테스트 구조: 어떤 계층까지 단위 테스트 작성이 필수인가?
- 컨트롤러의 역할: 인증, 파라미터 검증만 담당하고 UseCase 외 침범 금지
또한 정기적인 코드 리뷰나 설계 검토 회의에서 이 기준이 잘 지켜졌는지 검증해야 하며, 리뷰 포인트로 계층 침범, 포트 누락, 책임 모호성 등이 빠짐없이 포함되어야 한다. 이러한 운영 방식은 신규 인원의 온보딩에도 큰 도움을 준다.
8-4. 레거시 시스템에서의 점진적 전환 전략
이미 운영 중인 레거시 프로젝트에 Clean Architecture를 적용하는 것은 더욱 까다롭다. 하지만 이 또한 전체 재설계가 아닌 단계별 리팩토링 접근을 통해 충분히 실현 가능하다.
일반적인 전환 순서는 다음과 같다:
- 기존 Service 클래스에서 핵심 유스케이스를 분리하여 별도 클래스로 이동
- DB 호출을 Repository가 아닌 Output Port로 추상화하고, 기존 DAO는 어댑터로 이전
- 컨트롤러에서 유스케이스만 호출하도록 단순화
- 새로 도입하는 기능은 무조건 Clean 구조로 작성
이 과정에서 기술 부채가 많거나 얽힌 코드가 복잡한 경우, 테스트 코드를 먼저 작성한 뒤 리팩토링을 진행하면 안정성을 확보할 수 있다. 또한 도메인 별로 전환 일정을 나눠서 한 기능씩 구조화하는 것도 좋은 전략이다. 특히 SaaS 전환, API 공개, 마이크로서비스 분리 등을 고려 중인 조직이라면, 이 Clean 구조를 도메인 경계 기반 아키텍처 리팩토링의 전초 작업으로 활용할 수 있다.
'컴퓨터공학' 카테고리의 다른 글
SOLID 원칙 완전정복 (0) | 2025.05.21 |
---|---|
의존성 역전 원칙의 진짜 의미 (0) | 2025.05.21 |
헥사고날 아키텍처란? (0) | 2025.05.20 |
레이어드 아키텍처 구조 (0) | 2025.05.20 |
모놀리식 vs 마이크로서비스 아키텍처 (0) | 2025.05.20 |