클린아키텍처 - 입사 1개월 차의 시선으로 다시 읽어보다

2025. 12. 15. 00:23·도서관

 

예 맞아요 그 유명한 로버트 C, 마틴님이 작성한 그 책입니다. 사실 저번에 SOLID 관련된 내용은 

https://codeisfuture.tistory.com/86

 

클린아키텍처 - (좋은벽돌까지만이라도 만들어보자) 로버트C.마틴

왜 설계와 아키텍처를 고민해야 할까?작년 이맘때 처음 클린 아키텍처 책을 읽었는데, 이번에 다시 펼쳐본 이유는 명확했습니다.객체지향 설계에 대해 생각하는 기회가 생겼거든요 🤔로버트 C.

codeisfuture.tistory.com

이미 1편에서 썻어요..

 

그러면 왜 2편을 또 쓰냐??

1. 사실 뒷 부분 이해가 잘 안갔어요..정확히 1년2개월전에 읽고 쓸때 뭔말인지. 공감도 안됐어요

2. 입사 한달차가 되어가는데....네이버 부캠에서 완벽하게 정의내리지 못한 Usecase의 여파가 다시 오기 시작했어요..😅

 

"아키텍처의 목적은 시스템을 개발, 배포, 운영, 유지보수하는 데 드는 인력을 최소화하는 것이다"

 

1년 2개월 전, 처음 이 문장을 읽었을 때 솔직히 와닿지 않았어요. "그래서 뭐? 멋진 말이긴 한데..." 정도였죠.

그런데 입사 한 달차가 되고 나니, 이 문장이 왜 중요한지 진짜 진심으로 와닿아야 하는 상황이 생겼어요:))

아키텍처가 정말 해결하려는 문제

생각해보면 답은 간단해요. 제가 팀에 합류하고 나서 코드를 볼 때

  • "이 기능 어디 있더라...?" 하고 코드를 뒤지고
  • PR 올렸는데 충돌 나서 다른 선배님들께 여쭤보고
  • "이거 건드리면 다른 곳도 고쳐야 하는데..." 그런데 팀 룰은 다른 사람 코드를 건드릴때는 말해야하는데....

진짜 고민 많이 했어요. 다른 선배님들도 바쁜데 갓 들어온 신입이 계속 여쭤봐도 되나...

어느 정도에서 말을 해야 할까... 고뇌 * 1000

이게 바로 Uncle Bob이 말한 "개발자 시간을 잡아먹는 것들"이더라고요.

 

폴더 구조만 봐도 뭐하는 앱인지 알 수 있어야 한다

나쁜 구조의 문제점

예전에 과제 할 때 저도 이렇게 폴더링했어요:

ViewControllers/
Views/
Models/
Managers/

이거 보고 "이 앱 뭐하는 앱이야?" 물어보면 뭐라고 답할 건데요? "...iOS 앱?" 밖에 할 말이 없어요.

 

Use Case를 소리치게 하라

"아키텍처는 프레임워크에 대한 것이 아니다. 아키텍처는 Use Case에 대한 것이다"

근데 구조가 이렇다면?

Authentication/
  - Login/
  - Signup/
  - PasswordReset/
Shopping/
  - BrowseProducts/
  - AddToCart/
  - Checkout/
Payment/
  - ProcessPayment/
  - RefundPayment/
 

5분 안에 "아, 쇼핑몰 앱이구나!" 하고 파악이 되잖아요.

회사 코드 처음 봤을 때 이게 진짜 도움 됐어요.

폴더 구조만 봐도 이 앱이 뭘 하는지, 어떤 기능들이 있는지 바로 보이더라고요...(감사합니다><)

결정을 미루는 용기

처음부터 다 정하면 안 되는 이유

"좋은 아키텍트는 결정을 최대한 미룬다"

이 문장 처음 봤을 때 이해가 안 갔어요. 결정은 빨리 해야 되는 거 아니야? 근데 이런 상황 생각해보세요.

초기: "DB는 나중에 정하자" → 일단 파일 시스템에 저장
6개월 후: "잘 돌아가네? DB 필요 없겠다"

만약 처음부터 MySQL 골랐다면 설정하고 배우는 데만 3주 날렸을 거래요. 그런데 실제로는 필요 없었던 거죠.

제 상황도 비슷해요:

첫째 날: "DB? 나중에 정하고 일단 프로토콜로 정의만 해두자"
30일차: InMemoryRepository (개발용으로 빠르게 테스트)
90일차: SwiftDataRepository (이제 진짜 필요하니까)
180일차: RealmRepository (그냥 갈아끼우면 됨)

비즈니스 로직은 단 한 줄도 안 바뀌어요!

결국 DB건 Network건 UI건, 도메인 계층이 아닌 부분은 다 세부사항이에요. 30장 '데이터베이스는 세부사항'에서 말하는 게 바로 이거였어요.

 

Entity와 UseCase: 도메인의 🌸

Entity - 엔터프라이즈 비즈니스 규칙

Entity는 "가장 고수준의 규칙"이래요. 어떤 앱에서든, 심지어 앱이 아니어도 동일한 규칙이죠.

struct User {
    let email: String
    let password: String
    
    init(email: String, password: String) throws {
        guard email.contains("@") else {
            throw ValidationError.invalidEmail
        }
        guard password.count >= 8 else {
            throw ValidationError.weakPassword
        }
        self.email = email
        self.password = password
    }
}

이 규칙은 iOS 앱이든, 웹이든, Android든 똑같잖아요?

 

"이메일은 @를 포함해야 한다", "비밀번호는 8자 이상이어야 한다"는 변하지 않는 진리니까요.

UseCase - 애플리케이션 비즈니스 규칙

특정 앱만의 규칙이에요. Entity를 사용해서 특정 흐름을 만드는 거죠.

class RegisterUserUseCase {
    let repository: UserRepository
    let emailService: EmailService
    let analytics: AnalyticsService
    
    func execute(email: String, password: String) async throws {
        // 1. User Entity 생성 (검증)
        let user = try User(email: email, password: password)
        
        // 2. Repository에 저장
        try await repository.save(user)
        
        // 3. 웰컴 이메일 발송
        try await emailService.sendWelcome(to: user.email)
        
        // 4. Analytics 이벤트 기록
        analytics.track(.userRegistered)
    }
}

이 순서와 조합은 앱에서 정하는 고유한 정책이잖아요? 

뭐 어떤 앱들은 웰컴 이메일을 안보낼테니까요? 또 analytics를 안 쓸수도 있는거잖아요?

 

UseCase는 왜 DIP를 해야 할까?

여기까지는 이해가 갔어요

UseCase가 Repository 프로토콜에 의존하면, 나중에 SwiftData를 Realm으로 바꿔도 UseCase는 그대로잖아요
 DIP의 이유는 명확하죠

 

근데 문제는 여기서부터예요.

 

n개 공통 타입의 딜레마

10개 메모 타입이 있다고 가정해볼게요

  • TextMemo, ImageMemo, VoiceMemo, TodoMemo, LinkMemo, FileMemo, LocationMemo....

각각의 CRUD를 usecase마다 메서드를 만들어줬어요. 하나의 역할만 하도록 분리하면 10*5 = 최소 50개의 파일이 생기니까요

 

그런데 한 뷰에서 onAppear될때 저 메모들을 전부 다 가져와서 시간순으로 정렬해서 보여준다고 가정해볼게요(TCA로 예시들게요)

 

방법 1: Reducer가 10개 UseCase 의존

문제점:

  • Reducer가 10개 UseCase 의존 (의존성 폭발 💥💥💥💥💥💥)
  • 정렬 로직이 Reducer에 (비즈니스 로직이 Presentation 계층에)
  • 다른 Reducer에서도 같은 거 필요하면? 코드 중복
 
 

방법 2: 조합 UseCase 추가

protocol FetchAllMemosUseCase {
    func execute() async throws -> [Memo] // 정렬까지 완료
}

final class DefaultFetchAllMemosUseCase: FetchAllMemosUseCase {
    private let textMemoUseCase: TextMemoUseCase
    private let imageMemoUseCase: ImageMemoUseCase
    // ... 5개 더
    
    func execute() async throws -> [Memo] {
        // 7개 가져와서
        // 합치고
        // 정렬하고
    }
}

struct MemoListReducer: Reducer {
    let fetchAllMemosUseCase: FetchAllMemosUseCase // 의존성 1개!
}

근데 이상한 점:

  • 각 타입별 UseCase에 이미 fetchAll() 있는데?
  • UseCase가 다른 UseCase들을 의존? (계층이 UseCase → UseCase → Repository?)
  • 중복 아닌가?
 

결국 제가 계속 고민하고 있는 건 이거예요:

 

고민 1: 정렬은 누구 책임인가?

"메모를 시간순으로 정렬"하는 건 분명 비즈니스 로직이에요. Reducer에 있으면 안 될 것 같은데, 그럼 어디에?

 

고민 2: UseCase 개수가 폭발하는데?

 

고민 3: UseCase가 UseCase를 의존하는 게 맞나?

계층 구조가 이상해지는 거 아닌가? UseCase → Repository가 정석 아닌가?

 


소스 코드 의존성은 반드시 안쪽으로만 향해야하는데....

즉 고수준 컴포넌트는 저수준 컴포넌트에 의존하지 않고 둘다 추상화에 의존해야한다...

 

Repository는 공유 자원이었어요!

생각해보면 너무 당연한 건데 제가 너무 중복 코드에만 집착하고 있었던 거예요 🤦🏻‍♀️

여러 UseCase가 같은 Repository를 의존하는 게 뭐가 이상해요? 오히려 자연스러운 거잖아요!

 

UseCase는 "레고 조립자"라고 생각해볼게요

 

  • Repository: 레고 블록 보관함 (데이터 자판기)
  • UseCase: 목적에 맞게 조립하는 사람
  • 같은 블록을 여러 조립자가 쓸 수 있음!

 

오늘은 건담을 만들 거야. 그런데 건담 머리 블록이 A와 B 작품에서 겹칠 수도 있잖아요?

 

그러면 각 UseCase는 필요한 블록을 가져다 쓰는 거고, Repository는 그냥 블록 창고인 거예요.

 

"중복"이 아니라 "재사용"

// TextMemoUseCase에서
let memos = textRepo.fetchAll() // ← 자판기 버튼 누름

// FetchTimelineUseCase에서
let memos = textRepo.fetchAll() // ← 또 버튼 누름

이건 중복이 아니에요! Repository 재사용이에요.

복잡한 로직이 여러 곳에 있으면 중복이지만, 단순 호출은 중복이 아니에요. 23장 '프레젠터와 험블 객체'에서 말하는 것처럼, Repository는 "테스트하기 어려운 것(DB 접근)"을 캡슐화한 거니까 여러 곳에서 호출하는 게 당연한 거죠.

 

엔딩(느낀점..)

입사 한 달 차 신입의 고민이 1년 전 읽었던 책에 다 답이 있었네요.

 

핵심 3가지:

1. UseCase는 사용자 의도(User Action) 단위로 나눠라

  • TextMemo 관리 vs 전체 타임라인 = 다른 의도 = 다른 UseCase

2. Repository는 공유 자원이다

  • 여러 UseCase가 같은 Repository 쓰는 게 정상
  • UseCase끼리 의존하지 않고 모두 Repository에 의존

3. UseCase = 레고 조립자

  • Repository: 블록 보관함
  • UseCase: 목적에 맞게 조립
  • 같은 블록으로 다른 작품 만들 수 있음

 

혹시라도 틀린 내용이 있으면 따끔하게 댓글 남겨주세요

 

 

'도서관' 카테고리의 다른 글

함께 자라기[애자일로 가는 길]  (5) 2025.08.24
객체지향의 사실과 오해  (7) 2025.08.14
'도서관' 카테고리의 다른 글
  • 함께 자라기[애자일로 가는 길]
  • 객체지향의 사실과 오해
2료일
2료일
좌충우돌 모든것을 다 정리하려고 노력하는 J가 되려고 하는 세미개발자의 블로그입니다. 편하게 보고 가세요
  • 2료일
    GPT에게서 살아남기
    2료일
  • 전체
    오늘
    어제
    • 분류 전체보기 (138) N
      • SWIFT개발일지 (31)
        • ARkit (1)
        • Vapor-Server with swift (3)
        • UIkit (2)
      • 알고리즘 (25)
      • Design (6)
      • iOS (42)
        • 반응형프로그래밍 (12)
      • 디자인패턴 (6)
      • CS (3)
      • 도서관 (3) N
  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
2료일
클린아키텍처 - 입사 1개월 차의 시선으로 다시 읽어보다
상단으로

티스토리툴바