Encapsulate What Varies

Encapsulate What Varies 는 소프트웨어 설계에서 변화가 예상되는 부분과 그렇지 않은 부분을 분리하여, 변화가 생겨도 시스템 전체에 영향을 최소화하는 원칙이다. 이 원칙은 변화 가능성이 높은 코드 부분을 추상화하고 분리함으로써 시스템의 한 부분이 다른 부분과 독립적으로 변화할 수 있도록 한다. 객체지향 설계, 디자인 패턴, 시스템 아키텍처 등에서 반복적으로 활용되며, 변화에 유연하게 대응할 수 있는 구조를 만드는데 필수적이다. 대표적으로 전략 패턴 (Strategy Pattern), 데코레이터 패턴 (Decorator Pattern) 등에서 적용되며, 실무에서는 API 버전 관리, 플러그인 시스템, 설정 관리 등 다양한 형태로 활용된다.

핵심 개념

변하는 것을 캡슐화하라 (Encapsulate What Varies) 는 시스템에서 변화가 예상되는 부분을 식별하고 이를 안정적인 부분으로부터 분리하여 별도의 모듈로 캡슐화하여 시스템의 다른 부분에 영향을 주지 않도록 하는 설계 원칙 이다.

객체지향 프로그래밍, 함수형 프로그래밍, 시스템 아키텍처 등 소프트웨어 전반에 적용될 수 있으며, 전략 패턴, 팩토리 패턴, 플러그인 구조, 설정 파일 분리 등으로 구현할 수 있다.

핵심 아이디어

이론적 기반

배경

소프트웨어 개발에서 유일한 상수는 변화라는 명제에서 출발한다. 비즈니스 요구사항, 기술 환경, 사용자 요구가 지속적으로 변화하는 현실에서 이러한 변화에 효과적으로 대응할 수 있는 설계 원칙의 필요성이 대두되었다.

Gang of Four 의 디자인 패턴 책에서 “Consider what should be variable in your design” 라는 원칙으로 제시되었으며, 이는 기존의 재설계 원인에 초점을 맞추는 접근법과는 반대로 변경 가능한 요소를 미리 고려하는 접근법을 제시한다.

목적 및 필요성

주요 목적

  1. 변화 대응력 향상: 요구사항 변화에 신속하게 대응
  2. 시스템 안정성 확보: 변경이 다른 부분에 미치는 영향 최소화
  3. 유지보수성 개선: 코드 수정 시 영향 범위 제한
  4. 확장성 보장: 새로운 기능 추가 시 기존 코드 최소 변경

필요성

주요 기능 및 역할

특징

기본 특징

고급 특징

핵심 원칙

원칙명정의목적/핵심 의도실현 방법기대 효과
변화 식별 원칙 (Change Identification)시스템 내에서 자주 변경될 수 있는 요소를 사전에 식별무엇이 바뀌는지를 먼저 아는 것이 설계 분리의 전제 조건요구사항 분석, 기술/비즈니스 규칙 검토변화에 대한 대응 가능성 향상
분리 원칙 (Separation)변화하는 요소를 안정적인 부분으로부터 물리적, 논리적으로 분리영향 최소화 및 독립성 확보계층 분리, 모듈화, 서비스화독립적 테스트/배포 가능, 영향 최소화
추상화 원칙 (Abstraction)구체 구현이 아닌, 안정적인 인터페이스로 변화 요소 은닉인터페이스 교체만으로 구현 변경 가능하도록 설계Interface, Abstract Class, Protocol클라이언트 코드의 안정성 및 유연성 확보
결합도 최소화 원칙 (Loose Coupling)요소 간 직접 의존 최소화한 모듈의 변화가 다른 모듈에 영향을 주지 않도록의존성 주입 (DI), 이벤트, 메시지 큐확장성, 유연성, 테스트 용이성 향상
응집도 최대화 원칙 (High Cohesion)유사한 기능/변화 패턴을 하나의 단위로 묶음설계 명확화 및 로직 통일성 강화클래스/모듈 내 기능적 유사성 기준으로 그룹화유지보수성 및 코드 이해도 향상

주요 원리

graph TD
    A[요구사항 변화] --> B{변화 감지}
    B --> C[변화 영역 식별]
    C --> D[캡슐화 설계]
    D --> E[인터페이스 정의]
    E --> F[구현체 분리]
    F --> G[클라이언트 코드]
    
    G --> H{변화 필요?}
    H -->|Yes| I[새로운 구현체 생성]
    H -->|No| J[기존 동작 유지]
    
    I --> K[기존 인터페이스 유지]
    K --> L[구현체만 교체]
    L --> M[시스템 안정성 유지]
    
    J --> M
    
    style A fill:#ffcccc
    style D fill:#ccffcc
    style M fill:#ccccff
    
    subgraph "안정적인 부분"
        E
        G
        K
    end
    
    subgraph "변화하는 부분"
        F
        I
        L
    end

기본 작동 메커니즘

  1. 변화 식별: 시스템에서 자주 변경되는 코드 영역 감지
  2. 추상화 계층 생성: 변화하는 부분에 대한 안정적인 인터페이스 정의
  3. 구현 분리: 구체적인 구현을 인터페이스 뒤로 숨김
  4. 클라이언트 보호: 클라이언트 코드가 추상화에만 의존하도록 설계

변화 대응 프로세스

  1. 변화 요청 접수: 새로운 요구사항이나 변경 사항 발생
  2. 영향 범위 분석: 변화가 미치는 영향 범위 분석
  3. 캡슐화 경계 확인: 기존 캡슐화 경계의 적절성 검토
  4. 구현체 수정/추가: 새로운 구현체 생성 또는 기존 구현체 수정
  5. 시스템 안정성 확인: 다른 부분에 영향이 없음을 확인

구조 및 아키텍처

graph TB
    subgraph "클라이언트 계층 (Client Layer)"
        C1[클라이언트 A]
        C2[클라이언트 B]
        C3[클라이언트 C]
    end
    
    subgraph "추상화 계층 (Abstraction Layer)"
        I1[인터페이스 A]
        I2[인터페이스 B]
        I3[추상 클래스 C]
    end
    
    subgraph "구현 계층 (Implementation Layer)"
        IMPL1[구현체 A1]
        IMPL2[구현체 A2]
        IMPL3[구현체 B1]
        IMPL4[구현체 B2]
        IMPL5[구현체 C1]
    end
    
    subgraph "변화 관리 계층 (Change Management Layer)"
        CF1[설정 파일]
        DI1[의존성 주입 컨테이너]
        FM1[팩토리 매니저]
    end
    
    C1 --> I1
    C1 --> I2
    C2 --> I2
    C2 --> I3
    C3 --> I1
    C3 --> I3
    
    I1 -.-> IMPL1
    I1 -.-> IMPL2
    I2 -.-> IMPL3
    I2 -.-> IMPL4
    I3 -.-> IMPL5
    
    DI1 --> IMPL1
    DI1 --> IMPL2
    DI1 --> IMPL3
    DI1 --> IMPL4
    DI1 --> IMPL5
    
    CF1 --> DI1
    FM1 --> DI1
    
    style C1 fill:#e1f5fe
    style C2 fill:#e1f5fe
    style C3 fill:#e1f5fe
    style I1 fill:#f3e5f5
    style I2 fill:#f3e5f5
    style I3 fill:#f3e5f5
    style IMPL1 fill:#e8f5e8
    style IMPL2 fill:#e8f5e8
    style IMPL3 fill:#e8f5e8
    style IMPL4 fill:#e8f5e8
    style IMPL5 fill:#e8f5e8

구성 요소

구분구성요소기능역할특징
필수클라이언트 (Client)비즈니스 로직 실행추상화에 의존해 작업 수행구현체에 대해 모름 (구현 세부사항 은닉)
추상화 (Abstraction)안정적인 인터페이스 정의클라이언트와 구현체를 분리인터페이스, 추상 클래스 등으로 구성
구현체 (Implementation)실제 로직 수행추상화를 구현변경 가능성이 높고 독립적으로 변경 가능
선택팩토리 (Factory)구현체 생성 관리생성 로직 캡슐화생성 방식에 대한 유연성 제공
의존성 주입 컨테이너 (DI Container)의존성 자동 해결런타임 의존성 주입설정만으로 구현체 교체 가능, 생명주기 관리 포함
설정 관리자 (Configuration Manager)외부 설정 기반 구현체 결정런타임 동작 제어코드 수정 없이 구현체 변경 가능 (예: YAML, ENV, DB 기반 설정)

구현 기법

기법정의구성 요소목적장점적용 상황변화하는 부분실제 예시 (실무 활용 사례)
인터페이스 기반 추상화변화하는 기능을 인터페이스/추상 클래스로 추상화공통 인터페이스, 다중 구현체, 클라이언트 코드구현 은닉, 다형성구현체 교체 용이, 테스트 용이, 재사용성다양한 구현이 필요한 경우, 외부 교체 필요 시구체적 구현 방법, 기술 스택결제 시스템에서 PaymentProcessor 인터페이스로 다양한 결제 구현체 캡슐화
전략 패턴알고리즘/정책을 인터페이스로 분리, 런타임 교체 가능전략 인터페이스, 구체 전략, 컨텍스트 클래스동적 교체, 유연성조건문 제거, 확장성, 런타임 교체비즈니스 규칙/알고리즘이 자주 바뀔 때알고리즘, 정책, 비즈니스 규칙할인 정책, 인증 방식, 정렬 알고리즘 등 다양한 전략 캡슐화
팩토리 패턴객체 생성 로직을 팩토리로 분리, 생성과 사용을 분리팩토리 인터페이스, 구체 팩토리, 제품 인터페이스/클래스생성 로직 은닉, 확장성생성 방식 변경 용이, 결합도 감소객체 생성 방식이 자주 바뀌거나 다양할 때객체 생성 방법, 생성할 타입UI 컴포넌트, DB 커넥션, 알림 채널 등 다양한 객체 생성
데코레이터 패턴객체에 동적으로 기능을 추가하는 패턴기본 컴포넌트, 데코레이터 인터페이스, 구체 데코레이터유연한 확장기능 조합 자유, 코드 재사용성기능 확장/조합이 자주 필요한 경우추가 기능, 부가 기능커피/피자 옵션 추가, 웹 미들웨어 (로깅, 인증, 캐싱 등)
플러그인 구조기능 확장을 외부 모듈 (플러그인) 로 분리하는 아키텍처핵심 시스템, 플러그인 인터페이스, 플러그인 모듈확장성, 유연성기능 추가/제거 용이, 핵심 코드 단순화외부 기능 확장/모듈화가 필요한 시스템확장 기능, 외부 모듈CMS 플러그인, 브라우저 확장, 데이터 파이프라인 등
의존성 주입 (DI)객체의 의존성을 외부에서 주입하는 설계 기법의존성 인터페이스, 구현체, 주입 메커니즘, DI 컨테이너결합도 감소, 테스트테스트 용이, 교체/확장 쉬움, 관심사 분리환경별 구현 교체, 테스트가 중요한 경우의존 객체, 서비스서비스 계층 분리 (Spring/Angular), 테스트 시 Mock 주입, 환경별 서비스 구현체 주입

인터페이스 기반 추상화 (Interface-based Abstraction)

변화하는 부분을 인터페이스 (또는 추상 클래스) 로 추상화하여, 다양한 구현체를 통해 변화에 유연하게 대응하는 기법. 클라이언트는 인터페이스에만 의존한다.

구성:

실제 예시:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from abc import ABC, abstractmethod

# 인터페이스
class Logger(ABC):
    @abstractmethod
    def log(self, msg): pass

# 구현체 1
class FileLogger(Logger):
    def log(self, msg): print(f"파일 기록: {msg}")

# 구현체 2
class ConsoleLogger(Logger):
    def log(self, msg): print(f"콘솔 기록: {msg}")

# 클라이언트
def do_logging(logger: Logger):
    logger.log("로그 메시지")

# 사용 예시
do_logging(FileLogger())
do_logging(ConsoleLogger())

전략 패턴 (Strategy Pattern)

알고리즘이나 정책을 인터페이스로 분리하여, 런타임에 동적으로 교체할 수 있게 하는 패턴. 변화하는 알고리즘을 별도의 전략 객체로 캡슐화한다.

구성:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from abc import ABC, abstractmethod

# 전략 인터페이스
class ShippingStrategy(ABC):
    @abstractmethod
    def calculate(self, order):
        pass

# 구체적 전략 1
class FedExStrategy(ShippingStrategy):
    def calculate(self, order):
        return 10.0  # FedEx 배송비

# 구체적 전략 2
class UPSStrategy(ShippingStrategy):
    def calculate(self, order):
        return 8.0  # UPS 배송비

# 컨텍스트: 전략을 사용하는 주체
class ShippingCostCalculator:
    def __init__(self, strategy: ShippingStrategy):
        self.strategy = strategy  # 전략 객체 주입

    def calculate_cost(self, order):
        return self.strategy.calculate(order)

# 사용 예시
order = {"weight": 2}
calculator = ShippingCostCalculator(FedExStrategy())
print(calculator.calculate_cost(order))  # 10.0

의존성 주입 (Dependency Injection, DI)

객체가 직접 의존성을 생성하지 않고, 외부에서 주입받아 결합도를 낮추는 기법. 변화하는 구현체를 외부에서 주입받아 캡슐한다.
구성:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 인터페이스
class MessageSender:
    def send(self, msg):
        pass

# 서비스 구현체 1
class EmailSender(MessageSender):
    def send(self, msg):
        print("이메일 전송:", msg)

# 서비스 구현체 2
class SmsSender(MessageSender):
    def send(self, msg):
        print("SMS 전송:", msg)

# 클라이언트
class Notifier:
    def __init__(self, sender: MessageSender):  # 생성자 주입
        self.sender = sender

    def notify(self, msg):
        self.sender.send(msg)

# 인젝터 역할
notifier = Notifier(EmailSender())
notifier.notify("환영합니다!")

팩토리 패턴 (Factory Pattern)

객체 생성 로직을 별도의 팩토리 객체로 분리하여, 생성과 사용을 분리하는 패턴. 변화하는 객체 생성 방식을 캡슐화한다.
구성:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 제품 인터페이스(여기서는 명시적 인터페이스 대신 메서드 규약)
class Notification {
  send(msg) {}
}

// 구체적 제품 1
class EmailNotification extends Notification {
  send(msg) { console.log("이메일:", msg); }
}

// 구체적 제품 2
class SMSNotification extends Notification {
  send(msg) { console.log("SMS:", msg); }
}

// 팩토리
class NotificationFactory {
  static create(type) {
    if (type === "email") return new EmailNotification();
    if (type === "sms") return new SMSNotification();
    throw new Error("알 수 없는 타입");
  }
}

// 사용 예시
const notifier = NotificationFactory.create("email");
notifier.send("가입을 환영합니다!");

데코레이터 패턴 (Decorator Pattern)

객체에 동적으로 새로운 기능을 추가할 수 있도록 하는 패턴. 변화하거나 추가될 기능을 데코레이터 객체로 분리하여 캡슐화한다.
구성:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 컴포넌트 인터페이스
class Coffee:
    def cost(self):
        return 2000  # 기본 커피 가격

# 데코레이터 베이스
class CoffeeDecorator(Coffee):
    def __init__(self, coffee):
        self.coffee = coffee

    def cost(self):
        return self.coffee.cost()

# 구체적 데코레이터
class MilkDecorator(CoffeeDecorator):
    def cost(self):
        return super().cost() + 500  # 우유 추가

class SugarDecorator(CoffeeDecorator):
    def cost(self):
        return super().cost() + 300  # 설탕 추가

# 사용 예시
coffee = Coffee()
milk_coffee = MilkDecorator(coffee)
sugar_milk_coffee = SugarDecorator(milk_coffee)
print(sugar_milk_coffee.cost())  # 2800

플러그인 구조 (Plugin Architecture)

기능 확장을 외부 모듈 (플러그인) 로 분리하여, 시스템의 핵심과 확장 기능을 분리하는 구조. 변화하는 기능을 플러그인으로 캡슐화한다.
구성:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 플러그인 인터페이스(규약)
class Plugin {
  execute(data) {}
}

// 플러그인 구현
class PrintPlugin extends Plugin {
  execute(data) { console.log("출력:", data); }
}

// 코어 시스템
class CoreSystem {
  constructor() {
    this.plugins = [];
  }
  register(plugin) {
    this.plugins.push(plugin);
  }
  run(data) {
    this.plugins.forEach(plugin => plugin.execute(data));
  }
}

// 사용 예시
const core = new CoreSystem();
core.register(new PrintPlugin());
core.run("플러그인 테스트");

장점과 단점

구분항목설명
✅ 장점유연성 (Flexibility)기능, 정책, 구현체의 변경 및 추가가 구조에 영향을 주지 않고 가능함
유지보수성 (Maintainability)변화가 필요한 부분만 캡슐화되어 있으므로 수정이 국소화되고 위험이 감소함
확장성 (Extensibility)새로운 기능이나 구현체 추가 시 기존 코드 변경 없이 시스템 확장 가능
재사용성 (Reusability)추상화된 컴포넌트 또는 전략을 다양한 컨텍스트에서 재활용할 수 있음
테스트 용이성 (Testability)의존성이 분리되어 있어 단위 테스트, Mock 테스트, A/B 테스트가 용이함
관심사의 분리 (Separation of Concerns)안정적인 부분과 변화하는 부분이 구조적으로 분리되어 코드의 명확성과 가독성이 향상됨
설계 일관성 (Design Consistency)인터페이스, 추상 클래스 기반 설계로 전체 아키텍처의 통일성과 예측 가능성 확보
⚠ 단점초기 설계 복잡성 (Initial Design Complexity)캡슐화를 고려한 설계 구조 수립에 시간과 노력이 요구됨
과도한 추상화 (Over-Abstraction)예측되지 않는 변화까지 추상화할 경우 불필요한 인터페이스나 클래스가 증가
성능 오버헤드 (Performance Overhead)추상화 계층과 간접 호출로 인해 약간의 성능 저하 발생 가능
디버깅 복잡성 (Debugging Difficulty)추상화/위임 구조가 복잡해지면 호출 흐름 추적이 어려워질 수 있음
학습 곡선 (Learning Curve)디자인 패턴, DI, 전략, 추상화 개념을 팀원 모두가 충분히 이해해야 효과적
코드 스캐폴딩 증가 (Code Scaffolding Overhead)인터페이스, 구현체, 팩토리, 주입 설정 등 구조적 요소가 많아 초기 생산성이 낮아질 수 있음

도전 과제

도전 과제설명해결책
변화 예측의 어려움어떤 기능이 변화할지 정확히 식별하기 어려워 잘못된 캡슐화 발생 가능- 과거 변경 이력 분석 - 도메인 전문가와 협업 - 점진적 리팩토링 접근 적용
과도한 추상화필요 이상으로 추상화를 적용할 경우 오히려 복잡성이 증가- 실제 요구 기반 추상화 적용 - 사용 패턴 기반 검증 - 지나친 캡슐화는 피하고 YAGNI 원칙 준수
적절한 추상화 수준 판단의 어려움추상화 수준이 지나치게 일반적이거나 구체적일 경우 유연성 저하 또는 재사용 어려움- 반복적 리팩토링을 통한 조정 - 유스케이스 기반 설계 - 도메인 기반 모델링 적용
성능과 유연성 간의 트레이드오프추상화 계층으로 인한 호출 오버헤드 발생 가능- 중요 경로 (critical path) 선별적 최적화 - 정적 바인딩 고려 - 캐싱 및 경량 인터페이스 설계
팀원 간 설계 원칙 이해도 차이팀마다 OOP, DI, 전략 패턴 등에 대한 숙련도 격차로 인해 일관성 부족- 아키텍처 가이드 문서화 - 설계 리뷰/교육/워크숍 - 코드 리뷰 및 페어 프로그래밍 운영
과도한 인터페이스 생성단순히 추상화 원칙을 따르기 위해 의미 없는 인터페이스가 생성되는 경우- 역할 중심의 책임 기반 추상화 적용 - 의미 있는 경계 도출을 우선 고려 - 명확한 구현 책임이 있는 경우에만 분리
설계 비용 및 초기 생산성 저하설계 초기 단계에서 추상화 구조를 준비하는 데 시간과 자원이 소요됨MVP 수준에서는 구체 구현 우선 적용 후 점진적 캡슐화 - 리팩토링 기반 캡슐화 전략 활용
디버깅 복잡성 증가추상화 계층이 많아 호출 흐름 파악이 어려워 디버깅 난이도 상승- 호출 트레이싱 도구 활용 - 명확한 로깅 전략 수립 IDE 디버거와 연계한 계층 명시화

분류에 따른 종류

분류 기준유형/종류설명
설계 범위 기준클래스 수준 캡슐화클래스 내부에서 메서드 또는 속성의 변화를 은닉 (e.g. private 메서드)
모듈/컴포넌트 수준 캡슐화관련 클래스들을 모듈/컴포넌트로 묶어 내부 구현을 은닉
서비스 수준 캡슐화마이크로서비스 단위로 책임과 변화를 분리
시스템 수준 캡슐화서브시스템/계층 간 인터페이스를 정의하여 독립적 변경 가능
변화 대상 기준행위 (알고리즘/정책) 캡슐화계산 로직, 비즈니스 정책 등의 변경을 전략 객체로 분리
생성 방식 캡슐화객체 생성 방법의 변경을 팩토리로 추상화
상태 캡슐화객체의 상태 및 상태 전이 변화를 추상화하여 은닉
데이터 구조 캡슐화내부 데이터 표현 방식의 변경을 인터페이스로 은닉
외부 통신 캡슐화외부 시스템과의 연동 또는 메시징 방식의 변경을 Observer/Mediator 로 처리
설정 및 환경 의존 캡슐화런타임 설정, 환경별 차이 등을 외부 설정이나 DI 로 분리
구현 방식 기준인터페이스 기반구현 세부사항을 공통 인터페이스 뒤에 숨김
상속 기반추상 클래스를 통해 공통 로직 정의 후 하위 클래스에서 세부 구현
컴포지션 기반객체 간 조합으로 기능 위임, 유연한 구조 제공
전략 패턴 기반알고리즘을 캡슐화하여 런타임에 교체 가능
팩토리/생성자 패턴 기반객체 생성을 팩토리에 위임하여 생성 시점의 유연성 확보
데코레이터 기반기능을 동적으로 추가할 수 있도록 캡슐화
패턴 기반 분류Strategy (전략 패턴)알고리즘 또는 정책의 캡슐화 및 교체
Factory / Abstract Factory객체 생성 방식의 캡슐화
Template Method알고리즘 구조는 고정하되 일부 단계만 변경
Observer / Mediator통신 방식 캡슐화, 이벤트 및 중재자 패턴
State / Memento상태 변화 캡슐화, 상태 기반 동작 처리
Adapter / Proxy구조 변화 또는 접근 제어의 캡슐화
적용 목적 기준유연성 확보알고리즘, 구현, 통신의 변화에 유연하게 대응
테스트 용이성 확보Mock 기반 테스트, 인터페이스 주입 등을 통한 독립적 테스트 가능
재사용성 확보다양한 컨텍스트에서 재사용 가능한 구조 제공
유지보수성 향상변경이 지역적으로 발생, ripple effect 방지
시스템 확장성새로운 기능 추가 시 기존 코드 수정 최소화

실무 적용 예시

도메인변화 요소캡슐화 방법적용 기술/패턴적용 효과
결제 시스템결제 수단 (카드, 페이팔, 간편결제 등)PaymentProcessor 인터페이스 정의전략 패턴 (Strategy Pattern)결제 방식 추가/변경 시 기존 코드 변경 없음
인증 시스템인증 방식 (JWT, OAuth, LDAP 등)AuthenticationProvider 인터페이스 분리인터페이스 기반 추상화, DI인증 정책 간 유연한 전환 및 테스트 용이
알림 시스템알림 채널 (이메일, SMS, 슬랙, 푸시 등)NotificationChannel 인터페이스 정의전략 패턴, 옵저버 패턴다중 채널 대응 및 채널 교체 유연성 확보
로깅 시스템로그 출력 방식 (파일, 콘솔, DB, 클라우드)Logger 또는 LogAppender 인터페이스 분리데코레이터 패턴, DI, 팩토리 패턴로그 대상 변경 시 영향 최소화, 기능 확장 용이
데이터 접근데이터 소스 (RDB, NoSQL, CSV, API 등)DataSource, DAO 계층 정의DAO 패턴, DI다양한 소스 대응 및 DB 교체 시 영향 최소화
게임 개발캐릭터 행동 (공격, 회피, 특수기 등)Behavior 또는 Action 전략 인터페이스 정의전략 패턴, 상태 패턴캐릭터 클래스 재사용성 증가 및 동작 유연성 확보
전자상거래 플랫폼배송 로직 (택배, 퀵서비스, 해외배송 등)DeliveryStrategy 인터페이스전략 패턴배송 방식 확장 시 변경 최소화
데이터 파이프라인전처리/후처리 로직 (정규화, 필터링 등)Processor, Transformer 인터페이스템플릿 메서드 패턴 +

활용 사례

사례 1: 마이크로서비스 아키텍처

마이크로서비스 아키텍처는 시스템을 독립적으로 배포 가능한 서비스로 분해함으로써 가변성 캡슐화를 대규모로 적용한다. 각 서비스는 특정 비즈니스 기능을 캡슐화하며, 서비스 간 통신은 명확한 API 를 통해 이루어진다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 주문 서비스 API
@RestController
@RequestMapping("/orders")
public class OrderController {
    @PostMapping
    public OrderResponse createOrder(@RequestBody OrderRequest request) {
        // 주문 생성 로직
    }
    
    @GetMapping("/{id}")
    public OrderResponse getOrder(@PathVariable String id) {
        // 주문 조회 로직
    }
}

// 결제 서비스 API
@RestController
@RequestMapping("/payments")
public class PaymentController {
    @PostMapping
    public PaymentResponse processPayment(@RequestBody PaymentRequest request) {
        // 결제 처리 로직
    }
}

// 주문 서비스에서 결제 서비스 호출
@Service
public class OrderService {
    private final RestTemplate restTemplate;
    private final String paymentServiceUrl;
    
    // 생략…
    
    public Order createOrder(OrderRequest request) {
        // 주문 생성
        Order order = // …
        
        // 결제 서비스 호출
        PaymentRequest paymentRequest = new PaymentRequest(order.getId(), request.getAmount());
        PaymentResponse response = restTemplate.postForObject(
            paymentServiceUrl + "/payments",
            paymentRequest,
            PaymentResponse.class
        );
        
        // 결제 결과 처리
        // …
        
        return order;
    }
}

이 아키텍처에서는 결제 처리 방식이 변경되더라도 주문 서비스에는 영향을 미치지 않는다. 결제 서비스의 내부 구현은 API 계약을 유지하는 한 자유롭게 변경될 수 있다.

사례 2: 인프라스트럭처 추상화

현대 개발에서는 클라우드 제공업체, 데이터베이스, 메시징 시스템 등의 인프라스트럭처 의존성을 추상화하는 것이 중요하다. 이를 통해 특정 기술에 종속되지 않고 필요에 따라 인프라를 변경할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 스토리지 추상화
public interface StorageService {
    void store(String key, byte[] data);
    byte[] retrieve(String key);
    void delete(String key);
}

// AWS S3 구현
public class S3StorageService implements StorageService {
    private final AmazonS3 s3Client;
    private final String bucketName;
    
    // 생략…
    
    @Override
    public void store(String key, byte[] data) {
        s3Client.putObject(bucketName, key, new ByteArrayInputStream(data), new ObjectMetadata());
    }
    
    @Override
    public byte[] retrieve(String key) {
        S3Object object = s3Client.getObject(bucketName, key);
        return IOUtils.toByteArray(object.getObjectContent());
    }
    
    @Override
    public void delete(String key) {
        s3Client.deleteObject(bucketName, key);
    }
}

// 파일 시스템 구현
public class FileSystemStorageService implements StorageService {
    private final Path rootLocation;
    
    // 생략…
    
    @Override
    public void store(String key, byte[] data) {
        try {
            Files.write(rootLocation.resolve(key), data);
        } catch (IOException e) {
            throw new StorageException("Failed to store file", e);
        }
    }
    
    @Override
    public byte[] retrieve(String key) {
        try {
            return Files.readAllBytes(rootLocation.resolve(key));
        } catch (IOException e) {
            throw new StorageException("Failed to retrieve file", e);
        }
    }
    
    @Override
    public void delete(String key) {
        try {
            Files.delete(rootLocation.resolve(key));
        } catch (IOException e) {
            throw new StorageException("Failed to delete file", e);
        }
    }
}

// 서비스에서 사용
@Service
public class DocumentService {
    private final StorageService storageService;
    
    @Autowired
    public DocumentService(StorageService storageService) {
        this.storageService = storageService;
    }
    
    public void saveDocument(String id, byte[] content) {
        storageService.store("documents/" + id, content);
    }
}

이 예시에서는 스토리지 구현이 AWS S3 에서 파일 시스템으로 변경되더라도 DocumentService 는 수정할 필요가 없다.

사례 3: 기능 플래그 (Feature Flags)

기능 플래그는 런타임에 기능의 활성화 여부를 제어할 수 있는 현대적인 기법이다. 이는 새로운 기능의 점진적 출시와 A/B 테스트를 가능하게 한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 기능 플래그 서비스
public interface FeatureFlagService {
    boolean isEnabled(String featureName);
    boolean isEnabled(String featureName, String userId);
}

// 구현
@Service
public class FeatureFlagServiceImpl implements FeatureFlagService {
    private final FeatureFlagRepository repository;
    
    @Autowired
    public FeatureFlagServiceImpl(FeatureFlagRepository repository) {
        this.repository = repository;
    }
    
    @Override
    public boolean isEnabled(String featureName) {
        FeatureFlag flag = repository.findByName(featureName);
        return flag != null && flag.isEnabled();
    }
    
    @Override
    public boolean isEnabled(String featureName, String userId) {
        FeatureFlag flag = repository.findByName(featureName);
        if (flag == null || !flag.isEnabled()) {
            return false;
        }
        
        // 사용자별 점진적 출시 로직
        if (flag.getRolloutPercentage() < 100) {
            int hash = Math.abs(userId.hashCode()) % 100;
            return hash < flag.getRolloutPercentage();
        }
        
        return true;
    }
}

// 서비스에서 사용
@Service
public class PaymentService {
    private final FeatureFlagService featureFlagService;
    private final LegacyPaymentProcessor legacyProcessor;
    private final NewPaymentProcessor newProcessor;
    
    // 생략…
    
    public PaymentResult processPayment(String userId, Payment payment) {
        if (featureFlagService.isEnabled("new-payment-processor", userId)) {
            return newProcessor.process(payment);
        } else {
            return legacyProcessor.process(payment);
        }
    }
}

이 예시에서는 새로운 결제 처리 기능의 출시를 점진적으로 제어할 수 있다. 코드 변경 없이 구성만으로 어떤 사용자가 어떤 버전의 기능을 경험할지 결정할 수 있다.

사례 4: 의존성 주입 프레임워크

현대적인 소프트웨어 개발에서는 Spring, Guice 등의 의존성 주입 프레임워크를 사용하여 구현체의 변화를 캡슐화한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 구성 클래스
@Configuration
public class AppConfig {
    @Bean
    public PaymentGateway paymentGateway() {
        if (useStripe()) {
            return new StripePaymentGateway(stripeApiKey);
        } else {
            return new PayPalPaymentGateway(paypalClientId, paypalSecret);
        }
    }
    
    private boolean useStripe() {
        return "stripe".equals(environment.getProperty("payment.gateway"));
    }
}

// 서비스에서 사용
@Service
public class CheckoutService {
    private final PaymentGateway paymentGateway;
    
    @Autowired
    public CheckoutService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
    
    public OrderResult checkout(ShoppingCart cart) {
        // 결제 처리
        PaymentResult result = paymentGateway.processPayment(
            cart.getCustomerId(),
            cart.getTotalAmount()
        );
        
        // 결과 처리
        // …
    }
}

이 예시에서는 결제 게이트웨이의 구현이 Stripe 에서 PayPal 로 변경되더라도 CheckoutService 는 수정할 필요가 없다. 구성 변경만으로 다른 게이트웨이를 사용할 수 있다.

사례 5: 전자상거래 플랫폼의 결제 시스템 현대화

시나리오: 글로벌 전자상거래 플랫폼에서 다양한 국가별 결제 방식과 새로운 핀테크 솔루션을 지원해야 하는 상황이다. 기존 시스템은 신용카드 결제만 지원했지만, PayPal, Apple Pay, 암호화폐, 지역별 결제 솔루션 (알리페이, 카카오페이 등) 을 추가로 지원해야 한다.

시스템 구성:

graph TB
    subgraph "프레젠테이션 계층"
        UI[웹/모바일 결제 UI]
        API[결제 API Gateway]
    end
    
    subgraph "비즈니스 로직 계층"
        OS[주문 서비스]
        PS[결제 서비스]
        VS[검증 서비스]
    end
    
    subgraph "추상화 계층 - 캡슐화된 변화 부분"
        IPayment[IPaymentProcessor]
        IValidation[IPaymentValidator]
        INotification[INotificationService]
    end
    
    subgraph "구현체 계층 - 변화하는 부분"
        CC[신용카드 프로세서]
        PP[PayPal 프로세서]
        AP[Apple Pay 프로세서]
        CP[암호화폐 프로세서]
        KP[카카오페이 프로세서]
        
        EmailVal[이메일 검증]
        SMSVal[SMS 검증]
        BioVal[생체 인증 검증]
        
        EmailNoti[이메일 알림]
        SMSNoti[SMS 알림]
        PushNoti[푸시 알림]
    end
    
    subgraph "외부 시스템"
        Bank[은행 시스템]
        PG[PG사 시스템]
        Crypto[암호화폐 네트워크]
        Cloud[클라우드 서비스]
    end
    
    UI --> API
    API --> OS
    OS --> PS
    PS --> VS
    
    PS --> IPayment
    VS --> IValidation
    PS --> INotification
    
    IPayment -.-> CC
    IPayment -.-> PP
    IPayment -.-> AP
    IPayment -.-> CP
    IPayment -.-> KP
    
    IValidation -.-> EmailVal
    IValidation -.-> SMSVal
    IValidation -.-> BioVal
    
    INotification -.-> EmailNoti
    INotification -.-> SMSNoti
    INotification -.-> PushNoti
    
    CC --> Bank
    PP --> PG
    CP --> Crypto
    EmailNoti --> Cloud
    
    style IPayment fill:#ffeb3b
    style IValidation fill:#ffeb3b
    style INotification fill:#ffeb3b

Workflow:

sequenceDiagram
    participant C as 고객
    participant UI as 결제 UI
    participant PS as 결제 서비스
    participant PP as IPaymentProcessor
    participant PV as IPaymentValidator
    participant PN as INotificationService
    participant EXT as 외부 결제 시스템
    
    C->>UI: 결제 방식 선택
    UI->>PS: 결제 요청 (방식, 금액, 정보)
    
    PS->>PP: 결제 프로세서 팩토리에서 적절한 구현체 생성
    Note over PP: 런타임에 선택된 결제 방식에<br/>따라 구체적 구현체 결정
    
    PS->>PV: 결제 정보 검증
    PV->>PV: 선택된 검증 방식으로 검증 수행
    
    alt 검증 성공
        PS->>PP: 결제 처리 실행
        PP->>EXT: 외부 결제 시스템 호출
        EXT-->>PP: 결제 결과 응답
        PP-->>PS: 처리 결과 반환
        
        PS->>PN: 결제 완료 알림 발송
        PN->>PN: 설정된 알림 방식으로 발송
        
        PS-->>UI: 성공 응답
        UI-->>C: 결제 완료 안내
    else 검증 실패
        PV-->>PS: 검증 실패
        PS-->>UI: 오류 응답
        UI-->>C: 오류 메시지 표시
    end

역할 담당:

실무에서 효과적으로 적용하기 위한 고려사항 및 주의할 점

카테고리고려사항주의할 점권장 사항
요구사항 분석 단계변화 가능성 높은 영역 사전 식별변화 가능성 낮은 영역까지 과잉 설계하지 않기과거 변경 이력, 도메인 전문가 협업, 변화 예측 모델링 활용
설계 단계캡슐화 적용 범위 및 단위 결정모든 기능을 무분별하게 추상화하는 일관성 없는 접근실제 변화가 발생했거나 예측된 영역에만 최소한의 추상화 적용
인터페이스 또는 추상 클래스 설계너무 일반적이거나 구체적인 인터페이스 설계명확한 책임 분리, 단일 책임 원칙 기반 인터페이스 정의
구현 단계구현체와 추상화 계층 간 결합 최소화내부 구현 로직이 외부 추상화 계층에 노출되는 설계의존성 역전 원칙 (DIP) 과 인터페이스 분리 원칙 (ISP) 적용
객체 생성 방식의 유연성 확보객체 생성을 직접 하드코딩하여 캡슐화 효과 상실팩토리 패턴, DI 컨테이너 (Spring, NestJS 등) 활용
테스트 단계구현체별 독립 테스트 및 모킹 전략통합 테스트 시 실제 구현체 간 복잡한 의존관계 발생 위험전략 객체 Mock/Stubbing, 테스트 전용 DI 구성 활용
유지보수 단계새로운 구현체 추가 시 기존 시스템 영향 최소화인터페이스나 추상화 계층 수정 시 전파 영향 고려 부족최소 인터페이스 유지, 변경 포인트 격리화, SRP 기반 리팩토링
운영/성능 단계추상화 계층으로 인한 오버헤드 실측추상화 계층 제거로 유연성과 테스트성을 포기하는 경우APM, 프로파일링 도구를 통해 성능 병목 식별 후 국소적 최적화
협업 및 커뮤니케이션팀원 간 설계 원칙과 추상화 규칙 공유각자 다른 추상화 기준으로 시스템 일관성 저하정기 코드 리뷰, 설계 문서화, 팀 코딩 표준 수립
지속적 개선 및 리팩토링추상화 수준은 반복적 검토 대상초기에 설정한 구조가 변화에 따라 오래 유지될 것이라는 착각실제 사용 패턴 분석, 리팩토링 주기 설정, 필요 시 추상화 제거 또는 단순화

최적화하기 위한 고려사항 및 주의할 점

영역고려사항주의할 점권장 사항
런타임 객체 생성전략 객체나 구현체가 매 요청마다 생성될 경우 오버헤드 유발생성 시점과 범위를 고려하지 않으면 GC 와 성능 저하 발생싱글톤 또는 객체 풀링 활용, DI 컨테이너에서 재사용 객체 주입
동적 전략 선택조건문 기반 전략 선택 로직은 분기 수 증가 시 성능 저하 가능if-else/switch 남용 시 유지보수와 성능 모두 악화됨전략 맵핑 (HashMap/Enum 기반), 전략 팩토리 패턴 적용
간접 호출 구조인터페이스 기반 다형성은 JVM 에서 인라인 최적화가 어렵거나 지연됨HotSpot JIT 최적화가 제한됨성능 크리티컬 경로에서는 최적화된 직접 호출 또는 inlining 가능한 구조 사용
메모리 사용량다수 전략 구현체 또는 팩토리 등록 시 불필요한 메모리 소모전 전략 객체를 한 번에 로딩하거나 캐시할 경우 사용량 과도 증가Lazy Initialization 적용, 자주 사용하는 전략만 사전 생성
캐싱 전략반복 사용되는 구현체의 재사용 필요멀티스레드 환경에서 비스레드 안전 캐시 사용 시 동시성 문제Thread-safe 캐시 구조 또는 synchronized factory method 사용
JIT 최적화 한계동적 바인딩 시 컴파일러가 가상 메서드를 인라인하지 못할 수 있음제네릭을 피하고 런타임 타입을 사용하는 방식은 최적화 방해됨정적 타입 사용, sealed class, final class 등으로 JVM 최적화 유도
초기 로딩 비용DI 컨테이너 또는 팩토리가 초기 모든 구현체를 생성 시 초기 응답 지연실제 사용되지 않는 객체까지 생성되며 스타트업 성능 저하Lazy Injection, 프리로딩 전략 혼용 (ex. Spring @Lazy, @PostConstruct preload)
배치 처리 성능동일한 로직을 반복 실행할 경우 배치 처리 최적화 필요배치 단위 설정이 부적절할 경우 성능 이득 없음 또는 처리 지연 발생작업 크기 기반 튜닝, Stream 처리 또는 Worker Thread 병렬 분산 적용
GC 영향잦은 객체 생성/소멸은 GC 압박 증가메모리 누수 및 불필요한 객체 생명 주기 증가WeakReference, Object Pool, Flyweight 패턴 활용
불필요한 캡슐화과도한 추상화 구조는 메서드 호출/레벨 증가로 오버헤드 발생단순한 연산/로직에 추상화 적용 시 오히려 성능 저하 가능SRP/SOC 에 위배되지 않는 선에서 단순 캡슐화 또는 함수 인라인 전략 적용

주제와 관련하여 주목할 내용

분류항목설명
설계 원칙/패턴전략 패턴 (Strategy Pattern)런타임에 알고리즘 또는 정책을 동적으로 교체하기 위해 캡슐화
팩토리 패턴 (Factory Pattern)객체 생성 로직을 클라이언트로부터 분리하여 변화하는 생성 방식 캡슐화
템플릿 메서드 패턴고정된 알고리즘 틀에 대해 가변 서브 로직만 추상화하여 캡슐화 (상속 기반)
데코레이터 패턴 (Decorator Pattern)기능 추가/확장을 위한 객체 동적 래핑 캡슐화
DI (Dependency Injection)의존성 주입을 통해 구현체를 외부에서 주입받아, 객체 간 결합도 낮추고 변화 유연하게 대응
인터페이스 / 추상 클래스구현을 변경해도 클라이언트 코드 영향 없이 인터페이스만 유지함으로써 안정성 확보
기능 플러그인 구조화핵심 모듈과 별도로 동적으로 로딩 가능한 변화 영역 모듈화 구조화
아키텍처 스타일헥사고날 아키텍처 (Hexagonal)내부 로직 (도메인) 을 Port 와 Adapter 로 외부와 분리하여 외부 의존성의 변화를 캡슐화
Onion Architecture도메인 계층을 중심으로 의존성을 안으로 모으고, 외부는 인터페이스를 통해 의존하도록 구성하여 변화 격리
플러그인 아키텍처 (Plugin Arch.)핵심 시스템에 영향 없이 외부 기능 추가/변경 가능하도록 캡슐화된 모듈 구조
프로그래밍 패러다임함수형 프로그래밍고차 함수, 클로저, 커링 등을 통해 로직이나 동작을 값처럼 캡슐화하여 전달
클라우드/인프라사이드카 패턴 (Sidecar)마이크로서비스에서 로깅, 보안, 라우팅 등 횡단 관심사를 외부 Sidecar 로 분리하여 캡슐화
환경 설정 캡슐화 (Externalized Config)설정 값을 코드와 분리하여 환경 변경 시에도 코드 수정 없이 동작 가능
데이터/비즈니스CQRS명령 (Command) 과 조회 (Query) 를 분리하여 서로 다른 변화/요구를 독립적으로 대응 가능하도록 캡슐화
Repository Pattern도메인과 데이터 접근 기술 (JPA, SQL 등) 을 분리하여 데이터 저장소 변경에 유연하게 대응
보안/정책Zero Trust 아키텍처인증/인가/검증 등 보안 정책을 명확히 경계 지어 서비스 내부와 분리하고 캡슐화
정책 기반 라우팅/검증인증, 제한 조건 등을 외부 정책 서버 또는 미들웨어로 분리하여 핵심 로직과 분리
성능/운영캐시 어사이드 패턴 (Cache-Aside)읽기/쓰기 로직과 캐싱 로직을 분리하여 성능 최적화 전략 자체를 캡슐화
로깅 / 감사 / 트레이싱 분리핵심 로직과 무관한 로깅, 감사, 트레이싱 등을 별도 모듈 또는 Observer 로 구성하여 캡슐화

하위 주제로 분류해서 추가로 학습할 내용

카테고리주제설명
설계 패턴 (Design Pattern)전략 패턴 (Strategy Pattern)다양한 알고리즘을 런타임에 선택할 수 있도록 캡슐화
팩토리 패턴 (Factory Pattern)객체 생성 로직을 분리하여 생성 방식의 변화에 유연하게 대응
템플릿 메서드 패턴 (Template Method)상속 기반으로 고정 알고리즘과 가변 서브 로직을 분리
데코레이터 패턴 (Decorator Pattern)기존 객체를 수정하지 않고 새로운 기능을 동적으로 추가
상태 패턴 (State Pattern)객체의 상태에 따라 행동을 변경하며, 상태 전이를 캡슐화
아키텍처 패턴의존성 주입 (Dependency Injection)외부에서 구현체를 주입받아 결합도를 낮추고 테스트 가능성을 높임
인터페이스 기반 설계 (Interface-Based Design)인터페이스를 기준으로 클라이언트와 구현체를 분리하여 유연한 구조 구성
포트 - 어댑터 패턴 (Hexagonal Architecture)비즈니스 로직과 외부 입출력 간의 의존성을 분리
플러그인 아키텍처핵심 기능과 변경 가능 기능을 동적 모듈로 분리
테스트 및 품질Mock / Stub 기반 테스트캡슐화된 로직에 대해 독립적 테스트 수행을 위한 전략
테스트 주도 개발 (TDD)먼저 인터페이스 및 동작을 정의하고 테스트 기반으로 개발 진행
변경 이력 기반 리팩토링 전략변경 빈도, 커밋 로그 등 히스토리를 기반으로 캡슐화 대상 식별
소프트웨어 품질변경 가능성 예측 기법변경 빈도 분석, 모듈 책임 파악, 복잡도 분석 등을 통한 캡슐화 후보 도출
캡슐화 리팩토링 기법기존 코드에서 관심사 분리 및 인터페이스 추출 등 리팩토링 적용
운영 및 확장 전략설정 값 캡슐화 (Externalized Config)운영 환경 별 차이를 런타임 설정으로 분리하여 관리
다형성 전략 맵핑전략 패턴에서 구현체 선택을 조건문 대신 맵핑 테이블이나 DI 컨테이너로 효율화
성능 최적화 기법추상화 계층에서의 메모리, 오버헤드 문제 해결을 위한 캐싱/풀링/지연 로딩 전략 적용
실무 사례 중심 학습도메인별 전략 캡슐화 (결제, 인증 등)서비스마다 캡슐화가 필요한 다양한 전략 로직을 구체적으로 설계하고 적용
모듈 간 책임 분리 및 재사용공통 기능 (로깅, 감사, 에러 핸들링 등) 의 독립 모듈화
변화 주도 설계 기반의 캡슐화 적용DDD, 유비쿼터스 언어 기반의 변화 식별 및 구조 설계 전략

추가로 알아야 할 내용 및 관련 분야

설명관련 분야학습 주제
API 구조 변경에 유연하게 대응백엔드 개발REST API 추상화, 버저닝 전략, 인터페이스 캡슐화
동적 요청 처리 및 전략별 라우팅 구현MSA 아키텍처서비스 간 전략 선택 구조 (예: 다양한 결제 서비스 연동)
플러그인 형태로 기능 추가 지원모듈러 아키텍처OSGi, Microkernel 아키텍처, 메타프로그래밍
프론트엔드 렌더링 전략 분리프론트엔드 개발고차 컴포넌트 (HOC), Render Props, Composition API
의존성 주입과 서비스 등록의 자동화프레임워크 설계Spring, NestJS, Angular 의 DI 컨테이너
객체 재사용을 통한 성능 최적화성능 최적화객체 풀링 (Object Pooling), Lazy Initialization
불변성과 상태 캡슐화를 통해 변화 관리함수형 프로그래밍고차 함수 (HOF), 불변 객체, Currying, Composition
설계 구조의 변화 대응성 품질 측정소프트웨어 품질관리결합도 (Coupling), 응집도 (Cohesion), 변경 영향 분석
변경 가능한 비즈니스 규칙과 고정 도메인 간 분리도메인 주도 설계 (DDD)Bounded Context, Aggregate, Anti-corruption Layer
UI 상태 및 입력 전략 캡슐화프론트엔드 상태 관리상태 관리 전략 (Redux, Recoil, Pinia 등)
이벤트 기반 흐름의 전략적 분리비동기 시스템 설계이벤트 소싱 (Event Sourcing), Saga, CQRS
동적 기능 로딩 및 배포 자동화를 위한 구성 캡슐화클라우드 네이티브 설계12-Factor App, Externalized Configuration, Sidecar Pattern
요청 흐름, 인증, 로깅 등 횡단 관심사 분리서비스 메시 (Service Mesh)Envoy, Istio 를 통한 기능 캡슐화 및 분리
유연한 전략 기반 데이터 일관성 보장데이터 관리Eventual Consistency, Outbox Pattern, Transactional Outbox
변화 기반의 테스트 설계 전략테스트 아키텍처테스트 격리 (Mock, Stub), Contract Testing, 테스트 케이스 추상화

용어 정리

소프트웨어 설계 원칙 (Design Principles)

용어설명
OCP (Open-Closed Principle)소프트웨어는 확장에는 열려 있고, 변경에는 닫혀 있어야 한다는 원칙.
결합도 (Coupling)모듈/클래스 간의 의존 관계의 강도를 나타냄. 낮을수록 유연함.
응집도 (Cohesion)모듈 내부 구성 요소 간의 연관성 또는 집중도. 높을수록 유지보수에 유리.
제어 역전 (Inversion of Control, IoC)프로그램의 흐름 제어를 개발자가 아닌 프레임워크나 외부 구성요소가 수행하도록 위임하는 원칙.

객체 지향 프로그래밍 개념 (OOP Concepts)

용어설명
캡슐화 (Encapsulation)데이터와 행동을 하나로 묶고, 외부에서 내부 구현을 감추는 원칙.
추상화 (Abstraction)복잡한 내부 구현을 숨기고, 본질적인 특징만 드러내는 기법.
다형성 (Polymorphism)동일한 인터페이스를 통해 서로 다른 구현을 사용할 수 있는 능력.

디자인 패턴 (Design Patterns)

용어설명
전략 패턴 (Strategy Pattern)알고리즘/정책을 인터페이스로 캡슐화하고, 런타임에 교체 가능하게 함.
팩토리 패턴 (Factory Pattern)객체 생성 로직을 클래스 외부로 위임하여 캡슐화.
템플릿 메서드 패턴 (Template Method Pattern)알고리즘의 틀은 상위 클래스에서 정의하고, 세부 구현은 하위 클래스에서 수행.
데코레이터 패턴 (Decorator Pattern)객체에 기능을 동적으로 추가할 수 있도록 구성된 패턴.

의존성 및 구성 관리 (Dependency & Configuration)

용어설명
DI (Dependency Injection)객체가 필요로 하는 의존성을 외부에서 주입받는 구조. IoC 의 대표 구현 방식.
Feature Toggle (기능 토글)기능을 설정으로 On/Off할 수 있게 하는 기법. A/B 테스트, 배포 전략 등에서 사용.

테스트 및 유지보수 전략 (Testing & Maintainability)

용어설명
모킹 (Mocking)실제 객체 대신 가짜 (Mock) 객체를 사용하여 테스트하는 기법.
인터페이스 기반 개발변화에 유연하게 대응하기 위해 인터페이스를 먼저 정의하고 구현체를 분리하는 개발 방식.

아키텍처 및 시스템 설계 (Architecture & System Design)

용어설명
MSA (Microservices Architecture)애플리케이션을 작은 독립 서비스 단위로 나누어 운영하는 아키텍처.
플러그인 구조 (Plugin Architecture)기능을 독립된 모듈로 캡슐화하여 동적으로 로딩/교체 가능하게 함.

참고 및 출처

Encapsulate What Varies 원칙 관련

디자인 패턴 (Design Patterns)

의존성 주입 (Dependency Injection)

아키텍처 및 MSA

구성 및 런타임 전략