1. 프레임워크의 노예가 되지 마라
우리는 프로젝트를 시작할 때 습관적으로 이렇게 말합니다. "우리는 React 프레임워크를 쓸 거야." "우리는 Spring Boot 기반 프로젝트야." 하지만 로버트 C. 마틴(Uncle Bob)은 이렇게 일침을 놓습니다. "아키텍처는 도구(프레임워크)에 관한 것이 아니다. 아키텍처는 비즈니스 로직에 관한 것이다."
소프트웨어의 핵심 가치는 "사용자의 문제를 해결하는 로직"에 있지, "어떤 DB를 쓰는지", "어떤 웹 서버를 쓰는지"에 있지 않습니다. DB나 UI는 언제든 바뀔 수 있는 세부 사항(Detail)일 뿐입니다. 클린 아키텍처(Clean Architecture)는 이 "지저분한 세부 사항"으로부터 "고귀한 비즈니스 로직"을 철저히 격리하고 보호하기 위한 설계 철학입니다. 프레임워크는 도구지, 여러분의 주인이 아닙니다.
2. 의존성 규칙 (The Dependency Rule)
클린 아키텍처를 설명하는 가장 유명한 그림은 동심원(양파) 모양입니다. 이 그림에서 가장 중요한 규칙은 단 하나입니다.
"소스 코드 의존성은 반드시 안쪽(Inward)으로만 향해야 한다."
- 안쪽 원 (고수준): 업무 규칙, 정책, 엔티티. (가장 덜 변함, 가장 중요함)
- 바깥쪽 원 (저수준): DB, UI, 프레임워크, 외부 API, 디바이스. (가장 자주 변함)
안쪽 원에 있는 코드는 바깥쪽 원에 대해 아무것도 몰라야 합니다. Use Case(안쪽)는 SQL 쿼리(바깥쪽)가 어떻게 생겼는지, 심지어 DB가 오라클인지 MySQL인지도 몰라야 합니다. 데이터 포맷(JSON, XML)도 몰라야 합니다. 반대로 바깥쪽(Controller)은 안쪽(Use Case)을 알고 호출할 수 있습니다. 이것이 핵심입니다.
3. 4가지 핵심 계층 (Layers)
일반적으로 4개의 계층으로 나뉩니다. (안쪽 -> 바깥쪽 순서)
3.1. 엔티티 (Entities)
- 가장 안쪽의 핵심 업무 규칙입니다.
- "대출 이자는 원금의 3%로 한다", "비밀번호는 8자리 이상이어야 한다" 같은 전사적인 비즈니스 로직이 들어갑니다.
- 특정 애플리케이션에 종속되지 않으며, 코드를 React에서 Angular로 바꾸든, Spring에서 Node.js로 바꾸든 절대 변하면 안 되는 영역입니다.
User,Order,Loan같은 도메인 객체들이 여기에 해당합니다.
3.2. 유스케이스 (Use Cases)
- 애플리케이션 특화 업무 규칙입니다.
- "사용자가 '대출 신청' 버튼을 누르면 -> 신용 점수를 조회하고 -> 이자율을 계산해서 -> 승인 여부를 저장한다" 같은 흐름(Flow)을 제어합니다.
- 엔티티를 지휘(Orchestration)하여 목표를 달성합니다.
- 하지만 여전히 DB나 UI에 대해서는 모릅니다. 단지 "저장소(Repository) 인터페이스에 저장해라"라고 명령만 내립니다.
3.3. 인터페이스 어댑터 (Interface Adapters)
- 일종의 번역가(Translator) 역할을 합니다.
- UI의 데이터(Form Input, JSON)를 유스케이스나 엔티티가 이해할 수 있는 순수한 객체 포맷으로 변환하고,
- 유스케이스의 처리 결과를 다시 UI나 DB가 이해할 수 있는 포맷(SQL, View Model, HTML)으로 변환합니다.
- MVC의 Controller, Presenter, Gateway(Repository 구현체)가 여기 속합니다.
3.4. 프레임워크와 드라이버 (Frameworks & Drivers)
- 가장 바깥쪽, 즉 세상과 맞닿는 부분입니다.
- React, Vue, Spring Boot, MySQL, Oracle, AWS SDK 등이 여기 존재합니다.
- 우리는 이 영역에 비즈니스 로직을 작성하면 안 됩니다. 이곳은 단지 안쪽 원들과 세상을 연결해 주는 플러그(Plug)일 뿐입니다.
4. 의존성 역전 원칙 (DIP)의 마법
"아니, Use Case가 데이터를 저장하려면 DB를 호출해야 하는데, 어떻게 DB를 모를 수가 있죠?" 여기서 의존성 역전 원칙(Dependency Inversion Principle)이 등장합니다. 이것이 클린 아키텍처를 가능하게 하는 기술적 토대입니다.
- Use Case(안쪽)는
UserRepository라는 인터페이스(Interface)만 정의하고 호출합니다. ("나는 'save'라는 기능이 필요해! 누가 좀 해줘!") - DB 구현체(바깥쪽)는 이 인터페이스를 구현(Implements)합니다. (
UserRepositoryImpl) - 런타임에 의존성 주입(DI)을 통해 구현체가 인터페이스 자리에 끼워집니다.
이렇게 하면 소스 코드 상에서 Use Case는 DB 구현체를 import 하지 않습니다. 제어의 흐름(Flow of Control)은 안에서 밖으로 나가지만(호출하지만), 소스 코드 의존성은 밖에서 안으로 들어옵니다. 이것이 핵심입니다.
5. 소리치는 아키텍처 (Screaming Architecture)
좋은 아키텍처는 시스템의 목적을 큰 소리로 외쳐야 합니다.
도서관 시스템의 최상위 폴더를 열었을 때 Books, Members, Loans가 보여야 합니다.
만약 Controllers, Views, Models 같은 프레임워크 폴더 구조만 보인다면, 그것은 "나는 Spring Boot 앱이야!"라고 외치는 꼴입니다.
"이 시스템은 도서 관리 시스템입니다"라고 외치게 만드세요. 프레임워크는 그저 세부 사항일 뿐입니다.
6. 클린 아키텍처의 장점
이 복잡한 짓을 왜 할까요?
- 프레임워크 독립성: Spring Boot가 마음에 안 들면 내일 당장 다른 프레임워크로 갈아탈 수 있습니다. 비즈니스 로직은 건드릴 필요가 없습니다.
- 테스트 용이성 (Testability): UI나 DB 없이도 비즈니스 로직(Use Case)을 100% 테스트할 수 있습니다. Mock 객체만 있으면 됩니다. "테스트가 너무 느려요"라는 핑계가 사라집니다. TDD를 하기에 최적입니다.
- UI 독립성: 웹 앱을 모바일 앱이나 CLI 프로그램으로 바꾸기 쉽습니다. 로직은 그대로 두고 껍데기(UI)만 갈아 끼우면 됩니다.
- DB 독립성: MySQL을 쓰다가 Mongo DB로 바꾸고 싶나요? 바깥쪽의 Repository 구현체만 새로 짜면 됩니다. 안쪽 비즈니스 로직 파일은 단 한 줄도 고칠 필요가 없습니다.
7. 현실적인 적용 (Pragmatism)
"그럼 모든 프로젝트에 클린 아키텍처를 적용해야 하나요?" 아닙니다. 과유불급입니다. 간단한 CRUD 앱이나 토이 프로젝트, 수명이 짧은 프로토타입에 클린 아키텍처를 도입하면 파일 개수만 3배로 늘어나고 개발 속도는 느려집니다. (보일러플레이트 지옥)
하지만 프로젝트가 커지고, 팀원이 늘어나고, 유지보수 기간이 3년, 5년으로 길어진다면 클린 아키텍처는 빛을 발합니다. 처음에는 조금 느리더라도, 나중에는 기능을 추가하거나 수정하는 비용이 기하급수적으로 줄어들기 때문입니다. 유지보수 비용을 낮추는 것이 아키텍처의 진짜 목적입니다.
"빨리 가는 유일한 방법은, 제대로 가는 것이다." - Robert C. Martin