Featured image of post [Clean Architecture] 26. 독립성: 유스케이스, 운영, 개발, 배포

[Clean Architecture] 26. 독립성: 유스케이스, 운영, 개발, 배포

좋은 아키텍처가 제공해야 하는 네 가지 독립성을 다룹니다. 유스케이스, 운영, 개발, 배포의 독립성을 확보하는 방법과 중복의 함정을 피하는 전략을 설명합니다.

좋은 아키텍처는 다양한 독립성을 제공해야 한다. 유스케이스의 독립성, 운영의 독립성, 개발의 독립성, 배포의 독립성이 그것이다.

네 가지 독립성

flowchart TB
    ARCH[좋은 아키텍처]
    
    UC[유스케이스 독립성]
    OP[운영 독립성]
    DEV[개발 독립성]
    DEPLOY[배포 독립성]
    
    ARCH --> UC
    ARCH --> OP
    ARCH --> DEV
    ARCH --> DEPLOY

1. 유스케이스 독립성 (Use Case Independence)

각 유스케이스가 명확히 보여야 한다. 시스템의 의도가 아키텍처에서 드러나야 한다.

1
2
3
4
5
6
7
8
9
// 좋은 구조: 유스케이스가 명확히 보임
src/
├── usecases/
   ├── CreateOrderUseCase.java
   ├── CancelOrderUseCase.java
   ├── ProcessPaymentUseCase.java
   ├── RefundPaymentUseCase.java
   ├── ShipOrderUseCase.java
   └── TrackShipmentUseCase.java

“시스템의 아키텍처를 보면 유스케이스가 무엇인지 알 수 있어야 한다. 프레임워크가 아니라 유스케이스가 보여야 한다.”

2. 운영 독립성 (Operation Independence)

시스템이 필요로 하는 처리량과 응답 시간을 지원해야 한다.

flowchart LR
    subgraph ScaleOptions [확장 옵션]
        SINGLE[단일 서버]
        MULTI[멀티 서버]
        CLOUD[클라우드 자동 확장]
    end
    
    ARCH[좋은 아키텍처] --> SINGLE
    ARCH --> MULTI
    ARCH --> CLOUD
운영 요구사항아키텍처 지원
초당 10만 요청수평 확장 가능
밀리초 응답 시간캐싱 레이어 분리
99.99% 가용성페일오버 지원

3. 개발 독립성 (Development Independence)

여러 팀이 독립적으로 개발할 수 있어야 한다. 팀 간 조율 최소화.

flowchart TB
    subgraph Teams [독립적인 팀들]
        T1[주문 팀]
        T2[결제 팀]
        T3[배송 팀]
    end
    
    subgraph Components [독립적인 컴포넌트]
        C1[주문 컴포넌트]
        C2[결제 컴포넌트]
        C3[배송 컴포넌트]
    end
    
    T1 --> C1
    T2 --> C2
    T3 --> C3
1
2
3
4
5
6
7
8
9
// 각 팀이 독립적으로 작업 가능
// 주문 팀 - 주문 컴포넌트만 수정
package com.company.order;

// 결제 팀 - 결제 컴포넌트만 수정  
package com.company.payment;

// 배송 팀 - 배송 컴포넌트만 수정
package com.company.shipping;

4. 배포 독립성 (Deployment Independence)

컴포넌트를 독립적으로 배포할 수 있어야 한다. 전체 재배포 없이 일부만 변경.

flowchart LR
    subgraph Deploy [독립 배포]
        D1[주문 서비스 v1.2]
        D2[결제 서비스 v2.1]
        D3[배송 서비스 v1.0]
    end
    
    CHANGE[결제 로직 변경] --> D2
    D1 -.->|영향 없음| CHANGE
    D3 -.->|영향 없음| CHANGE

계층과 유스케이스

시스템을 분리하는 두 가지 방법이 있다: 수평 분할수직 분할.

수평 분할 (계층)

flowchart TB
    UI[UI 계층]
    BIZ[비즈니스 계층]
    DATA[데이터 계층]
    
    UI --> BIZ --> DATA
1
2
3
4
5
6
7
┌─────────────────────────────┐
│         UI 계층             │
├─────────────────────────────┤
│      비즈니스 계층          │
├─────────────────────────────┤
│       데이터 계층           │
└─────────────────────────────┘

수직 분할 (유스케이스)

1
2
3
4
┌─────────┬─────────┬─────────┐
│  주문   │  결제   │  배송   │
│ 유스케이스│ 유스케이스│ 유스케이스│
└─────────┴─────────┴─────────┘

결합: 계층 + 유스케이스

가장 좋은 방법은 두 가지를 결합하는 것이다.

flowchart TB
    subgraph Order [주문]
        O_UI[주문 UI]
        O_BIZ[주문 비즈니스]
        O_DATA[주문 데이터]
    end
    
    subgraph Payment [결제]
        P_UI[결제 UI]
        P_BIZ[결제 비즈니스]
        P_DATA[결제 데이터]
    end
    
    subgraph Shipping [배송]
        S_UI[배송 UI]
        S_BIZ[배송 비즈니스]
        S_DATA[배송 데이터]
    end
    
    O_UI --> O_BIZ --> O_DATA
    P_UI --> P_BIZ --> P_DATA
    S_UI --> S_BIZ --> S_DATA
1
2
3
4
5
6
7
┌─────────────┬─────────────┬─────────────┐
│  주문 UI    │  결제 UI    │  배송 UI    │
├─────────────┼─────────────┼─────────────┤
│ 주문 비즈니스│ 결제 비즈니스│ 배송 비즈니스│
├─────────────┼─────────────┼─────────────┤
│ 주문 데이터 │ 결제 데이터 │ 배송 데이터 │
└─────────────┴─────────────┴─────────────┘

중복의 함정

코드 중복처럼 보이는 것이 항상 나쁜 것은 아니다. 두 종류의 중복을 구분해야 한다.

진짜 중복 (True Duplication)

같은 이유로 변경되는 코드 → 통합 필요

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 진짜 중복: 하나로 통합해야 함
class UserValidator {
    boolean validateEmail(String email) {
        return email.contains("@");
    }
}

class AdminValidator {
    boolean validateEmail(String email) {  // 똑같은 코드!
        return email.contains("@");
    }
}

// 해결: 공통 유틸리티로 추출
class EmailValidator {
    static boolean validate(String email) {
        return email.contains("@");
    }
}

우발적 중복 (Accidental Duplication)

우연히 비슷해 보이지만 다른 이유로 변경분리 유지

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 우발적 중복 - 분리 유지!
// 지금은 같아 보여도 다르게 진화할 것

// 주문 팀용 DTO
class OrderDTO {
    String customerName;
    String address;
    // 주문 요구사항에 따라 변경됨
}

// 배송 팀용 DTO  
class ShipmentDTO {
    String customerName;
    String address;
    // 배송 요구사항에 따라 변경됨
    // 나중에 배송 추적 정보 추가될 수 있음
}

// 통합하면 안 됨! 각자 독립적으로 진화해야 함
flowchart TB
    subgraph TrueDup [진짜 중복]
        T1[같은 이유로 변경]
        T2[→ 통합]
    end
    
    subgraph AccDup [우발적 중복]
        A1[다른 이유로 변경]
        A2[→ 분리 유지]
    end
유형특징대응
진짜 중복같은 이유로 변경통합
우발적 중복다른 이유로 변경분리 유지

디커플링 모드

시스템을 분리하는 세 가지 수준:

flowchart TB
    subgraph Modes [디커플링 모드]
        SRC[소스 수준]
        BIN[바이너리 수준]
        SVC[서비스 수준]
    end
    
    SRC --> BIN --> SVC
모드분리 수준배포예시
소스 수준모듈/패키지하나의 실행 파일모놀리스
바이너리 수준jar/dll독립 배포 가능플러그인
서비스 수준네트워크완전 독립마이크로서비스

소스 수준 디커플링

1
2
3
4
// 하나의 실행 파일, 패키지로 분리
com.company.order/
com.company.payment/
com.company.shipping/

바이너리 수준 디커플링

1
2
3
4
app.jar
├── uses → order.jar
├── uses → payment.jar
└── uses → shipping.jar

서비스 수준 디커플링

1
2
[Order Service] <--HTTP--> [Payment Service]
                <--HTTP--> [Shipping Service]

어떤 모드를 선택할 것인가?

“좋은 아키텍처는 선택지를 열어둔다. 모놀리스로 시작해서 필요할 때 서비스로 분리할 수 있어야 한다.”

flowchart LR
    START[모놀리스로 시작]
    GROW[필요에 따라 성장]
    MICRO[마이크로서비스로 분리]
    
    START --> GROW --> MICRO
단계상황권장
초기요구사항 불명확모놀리스
성장팀 확장, 확장성 필요컴포넌트 분리
성숙독립 배포 필요서비스 분리

핵심 요약

독립성의미효과
유스케이스의도가 드러남이해하기 쉬움
운영확장 가능성능 충족
개발팀 독립병렬 작업
배포부분 배포빠른 릴리스

“좋은 아키텍처는 선택지를 열어둔다. 모놀리스로 시작해서 필요할 때 서비스로 분리할 수 있어야 한다.”