
개발을 시작한 지 얼마 되지 않았을 때 Clean Architecture를 검색하면 이런 그림을 자주 만나게 됩니다.
Entities
↑
Use Cases
↑
Interface Adapters
↑
Frameworks & Drivers
동그라미가 여러 개 있고,
Domain, Repository, UseCase, Presentation 같은 단어가 등장합니다.
그래서 처음에는 이렇게 생각하기 쉽습니다.
폴더를 저렇게 나누면 클린 아키텍처인가?
하지만 클린 아키텍처의 핵심은 폴더 구조가 아닙니다.
더 단순하게 표현하면 이겁니다.
바뀌기 쉬운 코드가 중요한 코드를 흔들지 못하게 만드는 것.
그리고 AI가 코드를 빠르게 만들어주는 시대에는 오히려 이 원칙이 더 중요해지고 있습니다.
앱을 처음 만들 때는 구조가 단순합니다.
예를 들어 사용자 정보를 가져온다고 해보겠습니다.
final class ProfileViewModel {
func loadProfile() async {
let url = URL(string: "https://api.example.com/profile")!
let (data, _) = try! await URLSession.shared.data(from: url)
let user = try! JSONDecoder().decode(User.self, from: data)
name = user.name
}
}
작은 앱에서는 별 문제가 없습니다.
하지만 기능이 늘기 시작합니다.
ProfileViewModel
├─ API 호출
├─ JSON Parsing
├─ Cache
├─ 로그인 상태 확인
├─ Analytics
├─ Error 처리
└─ 화면 상태 관리
어느 순간 ViewModel 하나가 모든 것을 알고 있습니다.
API가 바뀌어도 수정해야 하고,
Cache를 바꿔도 수정해야 하고,
테스트를 작성하려 해도 실제 Network가 필요합니다.
이때 생기는 질문이 있습니다.
화면 코드가 왜 서버 구현 방법까지 알아야 하지?
클린 아키텍처는 여기서 시작합니다.
클린 아키텍처에서 가장 중요한 규칙은 사실 복잡하지 않습니다.
의존성은 중요한 정책 쪽으로 향해야 합니다.
쉽게 그리면:
UI
↓
UseCase
↓
Domain
그리고 외부 시스템은 반대편에 있습니다.
API
Database
Firebase
Framework
Domain이 이런 것들을 직접 알게 만들지 않습니다.
즉:
Domain → Firebase
보다
Firebase
↓
Repository 구현
↓
Domain Interface
가 되는 구조입니다.
Robert C. Martin이 설명한 Clean Architecture 역시 UI, Database, Framework와 핵심 Business Rule을 분리하고 Source Code Dependency가 안쪽의 정책을 향하도록 하는 것이 중심입니다.
주니어 개발자가 가장 많이 하는 실수 중 하나입니다.
클린 아키텍처를 배웠다고 바로:
Presentation
Domain
Data
Infrastructure
Core
Common
Shared
Network
Repository
를 만듭니다.
그리고 파일 하나를 추가하려고 해도:
DTO
Mapper
Entity
Repository
RepositoryImpl
UseCase
Protocol
ViewModel
이 필요해집니다.
이건 Clean한 것이 아니라 복잡한 것일 수 있습니다.
클린 아키텍처에서 중요한 것은 Layer 개수가 아닙니다.
실제로 원래 Clean Architecture에서도 정확히 네 개의 원을 반드시 사용해야 한다는 규칙은 없습니다.
중요한 것은:
무엇이 자주 바뀌는가?
무엇이 오래 유지되어야 하는가?
어느 쪽이 어느 쪽을 알아야 하는가?
입니다.
앱에서는 처음에 이 정도만 구분해도 충분합니다.
Presentation
Domain
Data
화면과 사용자 Interaction입니다.
SwiftUI View
UIKit ViewController
ViewModel
UI State
Navigation
앱이 실제로 해야 하는 일입니다.
Entity
UseCase
Business Rule
Repository Protocol
데이터를 실제로 가져오는 방법입니다.
API
Database
Cache
DTO
Repository Implementation
흐름은:
View
↓
ViewModel
↓
UseCase
↓
Repository Protocol
↑
Repository Implementation
↓
API / DB
정도면 됩니다.
쇼핑 앱에 상품을 가져오는 기능이 있다고 해보겠습니다.
먼저 Domain입니다.
struct Product {
let id: String
let name: String
let price: Int
}
그리고 Repository의 역할만 정의합니다.
protocol ProductRepository {
func fetchProducts() async throws -> [Product]
}
Domain은 상품이 어디에서 오는지 모릅니다.
REST API인지
GraphQL인지
Firebase인지
Local DB인지
알 필요가 없습니다.
final class RemoteProductRepository: ProductRepository {
private let apiClient: APIClient
init(apiClient: APIClient) {
self.apiClient = apiClient
}
func fetchProducts() async throws -> [Product] {
let response: ProductResponse =
try await apiClient.request("/products")
return response.items.map {
Product(
id: $0.id,
name: $0.title,
price: $0.price
)
}
}
}
여기에는:
APIClient
JSON
DTO
Endpoint
가 있어도 됩니다.
왜냐하면 이 영역 자체가 외부 시스템과 연결하는 영역이기 때문입니다.
struct FetchProductsUseCase {
let repository: ProductRepository
func execute() async throws -> [Product] {
try await repository.fetchProducts()
}
}
지금은 너무 단순해 보입니다.
그래서 이런 생각이 들 수 있습니다.
Repository를 그냥 ViewModel에서 부르면 안 되나?
물론 됩니다.
UseCase가 아무 역할도 하지 않는다면 굳이 만들 필요 없습니다.
하지만 나중에 요구사항이 이렇게 바뀐다면 이야기가 달라집니다.
판매 중인 상품만 노출
재고 없는 상품 제외
사용자 등급별 가격 계산
차단 상품 제거
추천순 정렬
이건 API의 책임도 아니고 UI의 책임도 아닙니다.
앱이 수행해야 할 Business Rule입니다.
이때 UseCase의 존재 이유가 생깁니다.
여기서 가장 중요한 부분입니다.
클린 아키텍처를:
코드를 예쁘게 정리하는 방법
으로 이해하면 계속 헷갈립니다.
실제로는:
변경의 영향 범위를 제한하는 방법
에 가깝습니다.
예를 들어 서버가:
REST
↓
GraphQL
로 변경됐다고 해봅시다.
잘 분리되어 있다면:
Data Layer
를 주로 수정하면 됩니다.
UI가:
UIKit
↓
SwiftUI
로 바뀌어도 Domain은 대부분 그대로 남을 수 있습니다.
Database가:
Core Data
↓
SwiftData
로 바뀌어도 Business Rule까지 같이 뜯어고칠 이유는 없습니다.
좋은 Architecture의 목적은 변화를 없애는 것이 아니라 변화가 퍼지는 것을 막는 것입니다.
여기부터가 지금 더 중요한 이야기입니다.
예전에는 개발자가 직접 대부분의 코드를 작성했습니다.
지금은 Codex, Claude Code 같은 Coding Agent에게:
로그인 기능 만들어줘.
Repository 추가해줘.
Unit Test 만들어줘.
이 화면 SwiftUI로 바꿔줘.
라고 요청할 수 있습니다.
앞으로 AI가 작성하는 코드의 비율은 더 높아질 가능성이 있습니다.
그런데 여기서 새로운 문제가 발생합니다.
예를 들어 프로젝트가 이렇게 되어 있다고 합시다.
Features/
Domain/
Data/
Core/
개발자에게는 암묵적인 규칙이 있습니다.
View에서 Repository 직접 호출 금지
Domain에서 UIKit import 금지
DTO를 Presentation으로 전달 금지
Feature끼리 직접 참조 금지
Network는 Data Layer에서만 사용
그런데 AI에게 아무 설명도 하지 않고:
상품 상세 화면 추가해줘.
라고 하면 AI가 가장 빠른 방법으로:
struct ProductDetailView: View {
func load() async {
URLSession.shared...
}
}
를 만들 수도 있습니다.
코드는 동작합니다.
하지만 Architecture는 조금씩 무너지기 시작합니다.
AI 시대에는 이 문제가 더 빠르게 발생할 수 있습니다.
사람이 하루에 300줄을 만들던 프로젝트에서 Agent들이 훨씬 많은 코드를 생성하기 시작하면, 잘못된 설계 역시 더 빠르게 복제될 수 있기 때문입니다.
예전에는 팀의 시니어 개발자가 알고 있으면 어느 정도 운영이 됐습니다.
"여기서는 그렇게 만들면 안 돼."
"Repository 하나 만들어야 해."
"Domain에서 UIKit 쓰면 안 돼."
PR Review에서 알려주면 됐습니다.
AI Agent에게는 이 방법이 잘 통하지 않습니다.
그래서 Architecture를 명시적인 프로젝트 규칙으로 만들어야 합니다.
예를 들어:
ARCHITECTURE.md
를 만듭니다.
# Architecture
Presentation
- SwiftUI / UIKit
- ViewModel
- UI State
Domain
- Entity
- UseCase
- Repository Protocol
Data
- Repository Implementation
- API / DB
- DTO
## Dependency Rules
Presentation → Domain 허용
Data → Domain 허용
Domain → Presentation 금지
Domain → Data 금지
사람에게도 좋은 문서지만 AI에게는 더 중요합니다.
Codex에서는 프로젝트에 AGENTS.md를 둘 수 있고, Claude Code에서는 CLAUDE.md 같은 프로젝트 지침을 사용할 수 있습니다.
여기에 Build, Test, Coding Convention, Repository 규칙 등을 넣어 Agent가 작업을 시작할 때 참고하게 할 수 있습니다.
하지만 중요한 점이 있습니다.
AGENTS.md에 Architecture 전체를 복사해 넣는 것이 좋은 방법은 아닙니다.
오히려:
AGENTS.md
↓
Architecture Map
ARCHITECTURE.md
↓
설계 원칙
docs/
↓
상세 설계
Tests / Lint
↓
규칙 검증
처럼 역할을 나누는 편이 좋습니다.
OpenAI도 현재 Codex 프로젝트 지침에서 AGENTS.md를 간결하게 유지하고 Build/Test/Review 규칙과 Repository별 관습을 명시하는 방식을 권장하며, 더 큰 프로젝트에서는 이를 상세 문서로 연결하는 일종의 목차처럼 사용하는 방식을 소개하고 있습니다.
Claude Code 역시 CLAUDE.md를 매 세션 읽는 Project Instruction으로 사용하고, 특정 작업용 규칙은 Rules나 Skills 등으로 분리할 수 있습니다.
즉 AI 시대의 Architecture는 이제:
코드 구조
+
문서
+
AI Instruction
+
자동 검증
까지 같이 봐야 합니다.
이런 Instruction은 흔합니다.
Swift 6 사용
SwiftUI 사용
async/await 사용
MVVM 사용
물론 필요합니다.
하지만 Architecture를 유지하려면 더 중요한 규칙이 있습니다.
예를 들어:
## Architecture Rules
1. View는 UseCase만 호출한다.
2. Domain Layer에서는
SwiftUI, UIKit, URLSession을 import하지 않는다.
3. API Response DTO는
Presentation Layer까지 전달하지 않는다.
4. Repository Protocol은 Domain에 둔다.
5. Repository 구현은 Data에 둔다.
6. 새로운 Dependency를 추가하기 전에
기존 구현으로 해결 가능한지 확인한다.
7. 새로운 Layer를 임의로 만들지 않는다.
8. Architecture 변경이 필요하면
구현 전에 변경 이유를 먼저 설명한다.
이런 내용이 훨씬 중요합니다.
AI에게:
어떻게 코딩할 것인가
뿐 아니라
어디까지 변경해도 되는가
를 알려주는 것입니다.
문서만 있다고 Architecture가 유지되는 것은 아닙니다.
사람도 문서를 잊고 AI도 실수합니다.
그래서 가능하면 규칙을 자동화합니다.
예를 들어 Domain Module에:
import UIKit
이 들어가면 Build나 Lint 단계에서 잡도록 만들 수 있습니다.
Module Dependency도:
Presentation
↓
Domain
↑
Data
만 허용하도록 구성할 수 있습니다.
즉:
"이렇게 해주세요"
보다
"이렇게 하지 않으면 Build가 실패합니다"
가 훨씬 강합니다.
AI 시대에는 특히 그렇습니다.
좋은 구조는 AI가 매번 완벽한 판단을 해야 유지되는 구조가 아니라,
잘못된 코드를 만들어도 시스템이 잡아주는 구조입니다.
예전에는 테스트를:
사람이 코드를 수정하다 실수하는 것을 막는다.
정도로 생각했습니다.
Agent가 코드를 직접 수정하는 환경에서는 테스트가 하나의 작업 경계가 됩니다.
AI 코드 수정
↓
Build
↓
Unit Test
↓
Architecture Test
↓
UI Test
↓
Review
AI에게:
로그인 로직 수정해줘.
라고 말할 때
기대하는 것은 단순히 코드가 생성되는 것이 아닙니다.
기존 Test 통과
새 Requirement Test 추가
Architecture Rule 유지
Regression 없음
까지 확인되는 것입니다.
그래서 앞으로는 테스트가 많은 프로젝트일수록 Agent에게 일을 맡기기 쉬운 프로젝트가 될 가능성이 큽니다.
예를 들어 이런 요청은 위험합니다.
쇼핑몰 앱 결제 기능 전체를 구현해줘.
범위가 너무 큽니다.
대신 Architecture Boundary를 기준으로 나눕니다.
1. 요구사항 분석
2. Domain Model 제안
3. UseCase 설계
4. Repository Protocol 설계
5. Data 구현
6. Presentation 연결
7. Unit Test
8. Integration Test
그리고 단계마다 확인합니다.
Plan
↓
Review
↓
Implement
↓
Test
이 방식의 장점은 AI가 잘못된 방향으로 20개의 파일을 만든 뒤 처음부터 되돌리는 일을 줄일 수 있다는 것입니다.
예를 들어:
protocol PaymentRepository {
func payment(
productID: String,
amount: Int
) async throws -> PaymentResult
}
처럼 경계가 명확하면 AI에게:
PaymentRepository 구현을 Stripe에서 새로운 Payment API로 변경해줘.
라고 맡기기 쉽습니다.
AI가 수정해야 할 영역이 분명하기 때문입니다.
반대로:
Singleton
Global State
Massive ViewModel
거대한 Manager
숨겨진 Side Effect
가 많으면 AI도 전체 영향을 추적하기 어려워집니다.
사람에게 이해하기 어려운 코드는 AI에게도 안전하게 수정하기 어렵습니다.
그래서 AI 시대에 좋은 Architecture는:
Agent가 수정할 수 있는 범위를 명확하게 보여주는 Architecture
라고 볼 수도 있습니다.
앱이 커지면 단순히:
Views/
ViewModels/
Repositories/
UseCases/
처럼 기술 종류별로 나누는 것보다 Feature가 보이는 구조가 관리하기 편할 때가 많습니다.
예를 들어:
Features/
├─ Login/
│ ├─ Presentation/
│ ├─ Domain/
│ └─ Data/
│
├─ Product/
│ ├─ Presentation/
│ ├─ Domain/
│ └─ Data/
│
└─ Payment/
├─ Presentation/
├─ Domain/
└─ Data/
이렇게 하면 사람도 AI도:
결제 수정
→ Payment
로그인 수정
→ Login
처럼 변경 범위를 찾기 쉽습니다.
Architecture의 목적이 잘 드러나는 구조입니다.
이것도 중요합니다.
예를 들어 단순한 설정 화면이 있습니다.
알림 ON/OFF
Dark Mode ON/OFF
이걸 위해:
SettingEntity
UpdateSettingUseCase
SettingRepository
SettingRepositoryImpl
SettingDTO
SettingMapper
를 모두 만들 필요는 없습니다.
Architecture에는 비용이 있습니다.
파일이 늘고 Interface가 늘고 개발자가 이동해야 하는 코드도 늘어납니다.
그래서 기준을 이렇게 잡는 편이 현실적입니다.
Business Rule이 복잡하다
→ 분리
외부 Dependency가 자주 바뀐다
→ 분리
Test가 중요하다
→ 분리
여러 화면에서 사용한다
→ 분리
단순 UI 상태다
→ 단순하게 유지
Clean Architecture의 목적은 Layer를 많이 만드는 것이 아니라 변경 비용을 줄이는 것입니다.
AI 코드라고 별도의 Review 기준이 필요한 것은 아닙니다.
오히려 기존 Architecture 기준을 그대로 적용합니다.
잘못된 Layer를 참조하지 않는가?
ViewModel이 너무 많은 일을 하지 않는가?
Domain Rule이 UI에 들어가지 않았는가?
DTO가 Domain이나 UI까지 새어 나오지 않는가?
중요한 변경에 Test가 추가됐는가?
요청하지 않은 파일까지 수정하지 않았는가?
마지막 항목은 Agent 환경에서 특히 중요합니다.
매번 이렇게 말하고 있다면:
Repository Protocol은 Domain에 만들어줘.
DTO를 ViewModel에 넘기지 마.
Test 꼭 추가해줘.
문제가 있습니다.
이건 사람이 AI에게 반복적으로 Code Review를 하고 있는 것입니다.
두 번 이상 반복되는 지적이라면:
AGENTS.md
CLAUDE.md
ARCHITECTURE.md
Lint Rule
Test
중 하나로 옮기는 것이 좋습니다.
OpenAI의 Codex 가이드도 반복되는 Review Feedback이나 Agent의 잘못된 가정을 Project Instruction으로 남기고, 가능한 경우 Linter나 Type Check처럼 강제 가능한 시스템과 함께 사용하는 방식을 권장합니다.
즉:
AI가 실수
↓
사람이 수정
으로 끝내지 않고:
AI가 실수
↓
원인 파악
↓
Architecture Rule 추가
↓
다음 작업부터 예방
으로 바꾸는 것입니다.
이것이 AI 시대의 중요한 프로젝트 관리 방식 중 하나입니다.
AI Agent와 같이 개발한다면 프로젝트 문서를 역할별로 나누는 것도 좋습니다.
예를 들면:
README.md
프로젝트 소개와 실행 방법.
ARCHITECTURE.md
전체 구조와 Dependency Rule.
AGENTS.md
Codex가 반드시 알아야 할 작업 규칙.
docs/
├─ architecture/
├─ features/
├─ decisions/
└─ migrations/
상세 설계와 변경 기록.
여기에 중요한 Architecture 변경은 간단한 ADR 형식으로 남길 수 있습니다.
Decision
왜 변경했는가
어떤 대안을 검토했는가
무엇을 선택했는가
어떤 영향이 있는가
이 문서는 사람을 위한 기록인 동시에 AI가 다음 작업에서 참고할 수 있는 Context가 됩니다.
AI에게 많은 코드를 보여준다고 좋은 결과가 나오는 것은 아닙니다.
필요한 정보를 정확하게 제공하는 것이 중요합니다.
좋은 프로젝트라면 AI가 작업을 시작했을 때:
무슨 앱인지
어떤 Feature인지
Architecture가 무엇인지
Dependency Rule이 무엇인지
어떤 Test를 실행해야 하는지
어디까지 수정해도 되는지
를 빠르게 파악할 수 있어야 합니다.
그래서 앞으로 Architecture 문서는 단순한 개발 문서가 아니라 AI Agent에게 프로젝트의 Context를 제공하는 Infrastructure 역할도 하게 됩니다.
처음부터 거대한 Clean Architecture를 만들지 않아도 됩니다.
UI와 Network Code 분리
Business Logic을 ViewModel 밖으로 이동
Repository Protocol 도입
Domain이 외부 Framework를 모르도록 구성
Unit Test 추가
Architecture Rule 문서화
AI Agent Instruction 연결
이렇게 조금씩 만들어도 됩니다.
예전에는 이렇게 생각했다면:
Presentation
↓
Domain
↑
Data
지금은 한 단계가 더 필요합니다.
사람 / AI Agent
↓
Project Instructions
↓
Architecture
↓
┌────────────────────────────┐
│ Presentation │
│ ↓ │
│ Domain │
│ ↑ │
│ Data │
└────────────────────────────┘
↓
Test / Lint / Build
↓
Verification
여기서 중요한 건 AI가 아닙니다.
가운데 있는 Architecture와 Verification입니다.
AI는 이 규칙 안에서 일하는 또 하나의 개발자에 가깝습니다.
클린 아키텍처를 처음 배우면:
Entity
UseCase
Repository
Adapter
Dependency Inversion
같은 용어부터 외우기 쉽습니다.
하지만 가장 먼저 이해해야 할 것은 이것입니다.
중요한 코드를 자주 바뀌는 코드로부터 보호한다.
UI는 바뀝니다.
API도 바뀝니다.
Database도 바뀝니다.
Framework도 바뀝니다.
그리고 이제는 코드를 작성하는 주체도 바뀌고 있습니다.
사람이 작성하기도 하고 Codex나 Claude Code 같은 Agent가 작성하기도 합니다.
그래도 앱의 Business Rule과 Architecture 원칙은 쉽게 흔들려서는 안 됩니다.
그래서 AI 시대의 Clean Architecture는 단순히:
폴더를 잘 나누는 방법
이 아니라,
사람과 AI가
같은 규칙 아래에서
안전하게 코드를 변경하는 방법
으로 보는 편이 더 맞습니다.
코드를 만드는 속도가 빨라질수록 더 중요한 것은 코드를 많이 만드는 능력이 아닙니다.
어떤 코드는 어디에 있어야 하고, 무엇에 의존할 수 있으며, 어떤 변경은 허용하지 않을 것인지 명확하게 결정하는 능력입니다.
AI가 구현을 더 많이 맡게 될수록 Architecture의 역할은 줄어드는 것이 아니라 오히려 더 분명해질 가능성이 큽니다.

Robert C. Martin — The Clean Architecture
Clean Architecture의 Layer, Dependency Rule, Entity, Use Case, Interface Adapter 개념을 설명한 원문입니다.
The Clean Architecture 원문
Robert C. Martin — Screaming Architecture
프로젝트 구조가 Framework보다 실제 Use Case와 Business Domain을 드러내야 한다는 관점을 설명합니다.
Screaming Architecture 원문
OpenAI — AGENTS.md를 활용한 프로젝트 지침
Codex에 Build/Test 명령, Review 기준, Repository별 규칙 등을 지속적으로 전달하는 방법을 설명합니다.
Codex AGENTS.md 공식 문서
OpenAI — Harness Engineering
AGENTS.md를 거대한 설명서가 아니라 프로젝트 지식으로 이동하기 위한 목차처럼 사용하고, Architecture와 Design Document를 별도로 관리하는 실제 Agent-first Repository 운영 방식을 소개합니다.
Harness Engineering 공식 글
Anthropic — Claude Code 프로젝트 디렉터리
CLAUDE.md, Rules, Skills, Subagents 등의 역할과 프로젝트 수준에서 Agent Context를 관리하는 방식을 설명합니다.
Claude Code 공식 문서
공식 자료와 Clean Architecture의 원래 취지를 같이 보면, AI 시대라고 해서 새로운 Architecture가 갑자기 필요한 것은 아닙니다. 오히려 기존의 Dependency Rule과 명확한 Boundary에 Project Instructions, 문서, Test, Lint 같은 Agent용 Guardrail을 추가하는 방향이 현실적입니다.
결국 좋은 AI 개발 환경은 Agent에게 모든 판단을 맡기는 환경이 아니라, Agent가 잘못된 방향으로 가기 어렵도록 프로젝트 자체에 의도와 규칙이 남아 있는 환경에 가깝습니다.