Adapter Pattern

Adapter Pattern 은 기존의 인터페이스 (Adaptee) 를 클라이언트가 기대하는 인터페이스 (Target) 로 변환하는 구조 패턴이다. 어댑터 (Adapter) 는 Target 인터페이스를 구현하고 Adaptee 객체를 감싸 (합성) 거나 상속하여, 클라이언트가 요구하는 방식으로 기능을 제공한다. 이를 통해 기존 코드 수정 없이 다양한 외부 시스템, 레거시 코드, 라이브러리와의 통합이 가능하며, 코드의 재사용성과 유지보수성을 크게 높일 수 있다.

배경

현재 사용하고 있는 라이브러리가 더 이상 요구에 부합하지 않아 재작성하거나 다른 라이브러리를 사용해야 할 때, 어댑터 패턴을 이용해 기존 코드를 가능한 적게 변경하면서 새로운 라이브러리로 교체할 수 있다.
기존에 있는 시스템에 새로운 써드파티 라이브러리가 추가되거나 레거시 인터페이스를 새로운 인터페이스로 교체하는 경우에 코드의 재사용성을 높일 수 있다.

목적 및 필요성

주요 목적:

  1. 인터페이스 불일치 해결: 호환되지 않는 인터페이스 간의 연결
  2. 코드 재사용성 향상: 기존 코드 수정 없이 새로운 환경에서 활용
  3. 시스템 통합성 확보: 이질적인 시스템들의 원활한 협업
  4. 유지보수성 개선: 변경 영향 범위 최소화

필요성:

핵심 개념

어댑터 패턴은 호환되지 않는 인터페이스를 가진 클래스들이 함께 동작할 수 있도록 중간에 어댑터를 두어 인터페이스를 변환해주는 구조적 디자인 패턴이다.

기본 개념

  1. 인터페이스 변환 (Interface Translation): 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 것
  2. Wrapper 개념: 어댑터가 기존 객체를 감싸서 새로운 인터페이스를 제공하는 방식으로, Wrapper 패턴이라고도 불림
  3. 합성 vs 상속: 클래스 어댑터 (상속) 와 객체 어댑터 (합성) 두 가지 구현 방식
  4. 중간 계층 (Intermediary Layer): 코드와 레거시 클래스, 타사 클래스 간의 변환기 역할을 하는 중간 레이어

의도 (Intent)

한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다. 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있다.

다른 이름 (Also Known As)

동기 (Motivation / Forces)

적용 가능성 (Applicability)

패턴이 해결하고자 하는 설계 문제

  1. 인터페이스 불일치 문제

    • 서로 다른 시스템이나 라이브러리 간의 인터페이스 호환성 부족
    • 메서드 이름, 매개변수, 반환값 형식의 차이
  2. 레거시 시스템 통합 문제

    • 기존 시스템을 수정하지 않고 새로운 시스템과 연동 필요
    • 코드 재작성 비용과 리스크 최소화
  3. 의존성 관리 문제

    • 클라이언트 코드가 특정 구현체에 강하게 결합되는 문제
    • 시스템 간 결합도 증가로 인한 유지보수 어려움

문제를 해결하기 위한 설계 구조와 관계

graph TB
    subgraph "Problem Space"
        P1[인터페이스 불일치]
        P2[레거시 시스템 통합]
        P3[의존성 관리]
    end
    
    subgraph "Solution Structure"
        S1[Target Interface]
        S2[Adapter Class]
        S3[Adaptee Class]
    end
    
    subgraph "Design Relationships"
        R1[Inheritance/Implementation]
        R2[Composition/Aggregation]
        R3[Delegation Pattern]
    end
    
    P1 --> S1
    P2 --> S2
    P3 --> S3
    
    S1 --> R1
    S2 --> R2
    S3 --> R3

설계 구조 상세:

  1. 계층적 분리 (Layered Separation)

    • 클라이언트 계층과 서비스 계층 간의 명확한 분리
    • 각 계층의 독립적 발전 가능
  2. 인터페이스 추상화 (Interface Abstraction)

    • 구체적 구현으로부터 클라이언트 보호
    • 다형성을 통한 유연한 구현체 교체
  3. 위임 메커니즘 (Delegation Mechanism)

    • Adapter 가 실제 작업을 Adaptee 에게 위임
    • 책임의 명확한 분리

실무 구현 연관성

  1. 인터페이스 호환성 (Interface Compatibility)

    • 서로 다른 인터페이스를 가진 클래스들 간의 호환성 확보
    • 실무에서는 API 통합, 레거시 시스템 연동 시 필수적 개념
  2. 구조적 변환 (Structural Transformation)

    • 인터페이스 구조의 변환을 통한 시스템 통합
    • 실무에서는 데이터 형식 변환, 프로토콜 변환에 활용
  3. 위임 (Delegation)

    • Adapter 가 실제 작업을 Adaptee 에게 위임하는 메커니즘
    • 실무에서는 Wrapper 클래스 구현 시 핵심 원리
  4. 투명성 (Transparency)

    • 클라이언트가 Adapter 의 존재를 인식하지 못하는 투명한 연동
    • 실무에서는 시스템 간 결합도 최소화에 기여
개념실무 구현 측면연관성
인터페이스 호환성API Gateway, Service Mesh마이크로서비스 아키텍처에서 서비스 간 통신 표준화
구조적 변환Data Mapper, Message TranslatorETL 프로세스, 메시지 큐 시스템에서 데이터 변환
위임Proxy Pattern, Decorator PatternAOP(Aspect-Oriented Programming) 구현
투명성Interface Segregation, Dependency InjectionSOLID 원칙 준수, IoC 컨테이너 설계

주요 기능 및 역할

핵심 기능:

  1. 인터페이스 변환 (Interface Translation)

    • 클라이언트가 요구하는 인터페이스로 변환
    • 메서드 시그니처, 파라미터 형식 등 조정
  2. 데이터 변환 (Data Transformation)

    • 입력/출력 데이터 형식 변환
    • 프로토콜 간 데이터 구조 매핑
  3. 예외 처리 (Exception Handling)

    • Adaptee 의 예외를 Target 인터페이스에 맞게 변환
    • 오류 상황에 대한 적절한 대응

주요 역할:

특징

특징설명실무 적용 예시
투명성클라이언트가 Adapter 존재를 모름REST API Gateway 에서 내부 gRPC 서비스 호출
단방향성특정 방향으로만 변환 수행XML to JSON 변환기
조합성여러 Adapter 연쇄 사용 가능프로토콜 스택에서 다중 변환
확장성새로운 Adaptee 추가 용이결제 시스템에 새 PG 사 추가

핵심 원칙

  1. 단일 책임 원칙 (Single Responsibility Principle)

    • Adapter 는 오직 인터페이스 변환만 담당
    • 비즈니스 로직 추가 금지
  2. 개방 - 폐쇄 원칙 (Open-Closed Principle)

    • 새로운 Adaptee 추가 시 기존 코드 수정 불필요
    • 확장에는 열려있고 수정에는 닫혀있음
  3. 의존성 역전 원칙 (Dependency Inversion Principle)

    • 고수준 모듈이 저수준 모듈에 의존하지 않음
    • 추상화에 의존하는 구조
  4. 인터페이스 분리 원칙 (Interface Segregation Principle)

    • 클라이언트가 사용하지 않는 메서드에 의존하지 않음
    • 필요한 인터페이스만 노출

주요 원리

Adapter 가 중간자 역할, 메서드 호출을 받으면 내부에서 Adaptee 의 메서드 호출 흐름으로 변환

classDiagram
    Client --> Target
    Target <|.. Adapter
    Adapter o-- Adaptee
    Client --> Adapter: request()
    Adapter --> Adaptee: specificRequest()

Adapter 는 내부에서 interface call → adaptee conversion → method dispatch 흐름을 수행

작동 원리 및 방식

  1. 클라이언트 → Adapter 호출 (Target 메서드)
  2. Adapter 내부에서 Adaptee 변환 로직 수행
  3. Adaptee 메서드 호출
  4. Adapter 가 결과를 클라이언트에 반환

구성 요소

구성요소분류설명주요 역할특징
Target필수클라이언트가 기대하는 인터페이스 정의어댑터와의 계약 (클라이언트가 호출할 인터페이스)인터페이스 또는 추상 클래스 형태로 정의됨
Adapter필수Target 을 구현하며, 요청을 Adaptee 에 맞게 변환중재자 역할 수행Target 을 구현하고 Adaptee 를 포함하거나 상속
Adaptee필수기존 구현된 클래스 (기능 보유)실질적인 처리 담당외부 라이브러리 또는 변경이 어려운 코드
Client필수Target 을 통해 Adapter 에 요청을 보내는 사용자간접적으로 기능 사용Adapter 존재를 알지 못하며, Target 만 의존
ConcreteTarget선택Target 의 기본 구현체 (필요 시 사용)Target 기본 기능 구현추상 Target 을 직접 사용할 수 없는 경우 사용됨
Multiple Adaptees선택하나의 어댑터가 여러 Adaptee 인스턴스를 처리다양한 Adaptee 를 공통 Target 에 맞게 변환어댑터 내부에 전략 또는 매핑 로직 포함됨

Object Adapter 방식

sequenceDiagram
    participant Client
    participant Adapter
    participant Adaptee
    
    Client->>Adapter: request()
    Adapter->>Adapter: validateInput()
    Adapter->>Adaptee: specificRequest()
    Adaptee->>Adapter: result
    Adapter->>Adapter: transformResult()
    Adapter->>Client: transformedResult

Adapter Pattern 의 구조는 다음과 같은 핵심 구성요소들로 이루어진다:

classDiagram
    class Target {
        <<interface>>
        +request()
    }

    class Adapter {
        +request()
        -adaptee : Adaptee
    }

    class Adaptee {
        +specificRequest()
    }

    class Client {
        -target : Target
        +execute()
    }

    class ConcreteTarget {
        +request()
    }

    Target <|-- Adapter
    Adapter --> Adaptee
    Client --> Target
    Target <|-- ConcreteTarget

Class Adapter 방식

classDiagram
    class Target {
        <<interface>>
        +request()
    }

    class Adaptee {
        +specificRequest()
    }

    class Adapter {
        +request()
    }

    class Client {
        -target : Target
        +execute()
    }

    Adapter <|-- Target
    Adapter <|-- Adaptee
    Client --> Target

패턴 적용의 결과와 트레이드오프

긍정적 결과:

트레이드오프:

구현 기법

구현 기법핵심 개념구성 요소특징 및 장점주요 사용 언어대표 사용 사례
Object Adapter합성 (Composition) 기반Adapter 가 Target 인터페이스 구현 - Adaptee 를 내부 변수로 포함- 단일 상속 언어에서 유리 - 유연한 Adaptee 교체 - 테스트 용이Java, Python, JS외부 API 어댑터, 마이크로서비스 연동
Class Adapter상속 (Inheritance) 기반Adapter 가 Target, Adaptee모두 상속- 메모리 효율적 - Adaptee 의 모든 기능 직접 사용 가능 - 다중 상속 필요C++, PythonDB 드라이버 어댑터, 그래픽 인터페이스
Two-way Adapter양방향 변환 가능Adapter 가 Target, Adaptee 인터페이스 모두 구현 - 양방향 변환 메서드 포함- 서로 다른 시스템 간 상호 변환 가능 - 통신 양방향 대응Python, C++Protocol ↔ API 변환, Dual API 어댑터
Interface Adapter부분 구현- 추상 클래스를 기반으로 일부 메서드만 오버라이딩 Default 메서드 제공 가능- 인터페이스가 너무 클 때 유리 - 필요한 기능만 선택적으로 구현Java, Python이벤트 핸들러, 미디어 플레이어
Adapter Registry / Factory동적 인스턴스 관리 및 검색Adapter 인스턴스를 저장하고 키 기반으로 제공 - 다형성 및 전략 패턴과 혼용 가능- 런타임 어댑터 선택 - 다양한 Adaptee 관리 가능 OCP 준수 가능Java, Python멀티 PG 연동, 멀티 클라우드 SDK 연동

Object Adapter (객체 어댑터)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Target 인터페이스
class MediaPlayer:
    def play(self, file_name):
        raise NotImplementedError()

# Adaptee 클래스 (기존 구현)
class VLCPlayer:
    def play_vlc(self, file_name):
        print(f"VLC 플레이어로 재생: {file_name}")

# Adapter 클래스 (객체 어댑터 방식)
class MediaAdapter(MediaPlayer):
    def __init__(self, adaptee):
        self.adaptee = adaptee  # 기존 VLCPlayer 객체 포함

    def play(self, file_name):
        # Target 인터페이스의 play() 호출 시 adaptee 의 play_vlc() 위임
        self.adaptee.play_vlc(file_name)

# 사용 예
player = MediaAdapter(VLCPlayer())
player.play("movie.vlc")

Class Adapter (클래스 어댑터)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Target 인터페이스
class MediaPlayer:
    def play(self, file_name):
        raise NotImplementedError()

# Adaptee 클래스
class MP4Player:
    def play_mp4(self, file_name):
        print(f"MP4 파일 재생: {file_name}")

# Adapter 클래스 (클래스 어댑터 방식, 다중 상속)
class MP4Adapter(MediaPlayer, MP4Player):
    def play(self, file_name):
        # Target 인터페이스의 play()를 Adaptee 메서드로 매핑
        self.play_mp4(file_name)

# 사용 예
player = MP4Adapter()
player.play("music.mp4")

Two-Way Adapter (양방향 어댑터)

 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
# 두 인터페이스 정의
class ModernAPI:
    def request_json(self, data): pass

class LegacyAPI:
    def request_xml(self, xml_data): pass

# 두 구현체
class ModernService(ModernAPI):
    def request_json(self, data):
        print(f"신형 API: JSON 처리 - {data}")

class LegacyService(LegacyAPI):
    def request_xml(self, xml_data):
        print(f"구형 API: XML 처리 - {xml_data}")

# Two-way Adapter
class DualAdapter(ModernAPI, LegacyAPI):
    def __init__(self):
        self.legacy = LegacyService()
        self.modern = ModernService()

    def request_json(self, data):
        # JSON → XML 변환 후 호출
        xml_data = f"<data>{data}</data>"
        self.legacy.request_xml(xml_data)

    def request_xml(self, xml_data):
        # XML → JSON 변환 후 호출
        json_data = xml_data.replace("<data>", "").replace("</data>", "")
        self.modern.request_json(json_data)

# 사용 예
adapter = DualAdapter()
adapter.request_json("Hello JSON")
adapter.request_xml("<data>Hello XML</data>")

Interface Adapter (인터페이스 어댑터)

 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
# 인터페이스 (다수의 메서드)
class MediaInterface:
    def play_audio(self, file): pass
    def play_video(self, file): pass
    def stream(self, url): pass

# 어댑터 기본 클래스 (Default 구현 제공)
class AbstractMediaAdapter(MediaInterface):
    def play_audio(self, file): pass
    def play_video(self, file): pass
    def stream(self, url): pass

# 오디오 전용 어댑터 구현
class AudioAdapter(AbstractMediaAdapter):
    def __init__(self, audio_player):
        self.audio_player = audio_player

    def play_audio(self, file):
        print(f"오디오 어댑터로 {file} 재생")
        self.audio_player.play(file)

# 사용 예
class SimpleAudioPlayer:
    def play(self, file):
        print(f"단순 오디오 플레이어: {file}")

adapter = AudioAdapter(SimpleAudioPlayer())
adapter.play_audio("music.mp3")

Adapter Factory / Registry

 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
# 어댑터 베이스
class PaymentAdapter:
    def process_payment(self, amount): raise NotImplementedError()

# Adapter 1
class PayPalAdapter(PaymentAdapter):
    def process_payment(self, amount):
        print(f"PayPal 결제 처리: {amount}원")

# Adapter 2
class KakaoPayAdapter(PaymentAdapter):
    def process_payment(self, amount):
        print(f"KakaoPay 결제 처리: {amount}원")

# Adapter Registry
class AdapterFactory:
    _registry = {}

    @classmethod
    def register(cls, name, adapter_cls):
        cls._registry[name] = adapter_cls

    @classmethod
    def get_adapter(cls, name):
        if name not in cls._registry:
            raise Exception(f"No adapter found: {name}")
        return cls._registry[name]()

# 등록
AdapterFactory.register("paypal", PayPalAdapter)
AdapterFactory.register("kakaopay", KakaoPayAdapter)

# 사용
adapter = AdapterFactory.get_adapter("paypal")
adapter.process_payment(5000)

요약 비교

구현 기법방식특징 요약실무 사용 예시
Object Adapter합성 기반유연성 높고 일반적인 방식외부 API, SOAP 연동
Class Adapter다중 상속성능 우수, Adaptee 기능 직접 활용 가능C++ 드라이버, 그래픽 렌더링
Two-way Adapter양방향 변환상호 변환 가능, 컨텍스트 의존양방향 통신, SDK ↔ REST 호환
Interface Adapter부분 구현필요한 메서드만 구현 가능이벤트 리스너, UI 처리
Factory/Registry런타임 관리Adapter 동적 선택 및 관리 가능PG 연동, 클라우드 API 변환

장점

구분항목설명기여 요인 / 설계 원칙
호환성인터페이스 호환성서로 다른 인터페이스를 가진 시스템이나 클래스가 함께 동작 가능어댑터의 인터페이스 변환 기능
재사용성기존 코드 재사용검증된 Adaptee 로직을 수정 없이 재활용 가능기존 클래스 유지, 중복 제거
캡슐화클라이언트 코드 변경 최소화클라이언트는 어댑터를 통해 동작하므로 기존 코드 수정 없이 새 기능 통합 가능인터페이스 기반 설계
유연성다양한 Adaptee 연동 지원다양한 구현체나 외부 시스템을 동일한 인터페이스로 통합 가능다형성, 어댑터 확장성
확장성신규 Adaptee 확장 용이어댑터만 추가하면 새로운 외부 시스템도 통합 가능개방 - 폐쇄 원칙 (OCP), 구성 기반 확장
테스트 용이성Mock 어댑터 지원어댑터를 Mock 객체로 대체하여 단위 테스트 수행 가능테스트 격리, 유닛 테스트 강화
설계 원칙 준수단일 책임 원칙 (SRP)어댑터는 변환 로직만 담당하여 인터페이스와 비즈니스 로직을 분리책임 분리
설계 원칙 준수개방 - 폐쇄 원칙 (OCP)어댑터 추가만으로 기능 확장 가능하며 기존 로직 수정 불필요모듈화 및 인터페이스 기반 설계
구조적 이점시스템 분리성클라이언트와 서비스 구현체 간의 결합도 감소, 변경 전파 최소화중간 계층을 통한 추상화
사용자 관점 편의투명성클라이언트는 어댑터 존재를 의식하지 않고 기존 방식대로 사용 가능인터페이스 일치로 인한 무중단 통합

단점 및 문제점

단점

항목설명해결 방안
클래스 수 증가어댑터 클래스가 많아지면 구조가 복잡해지고 유지보수가 어려워짐명확한 네이밍 규칙, 패키지 분리, 모듈화 설계 적용
성능 오버헤드중간 계층을 추가함으로써 호출 체인이 길어져 실행 성능이 저하될 수 있음불필요한 로직 제거, 핵심 기능만 래핑, 캐싱 및 배치 처리 도입
디버깅 어려움여러 계층을 거친 호출로 인해 로그 추적과 디버깅이 복잡해짐로그 체계 강화, 분산 트레이싱 도입 (OpenTelemetry 등), 단계별 검증 삽입
강결합 유도Adaptee 에 대한 직접적인 의존도가 높아지면 유연성이 감소할 수 있음인터페이스 기반 설계, 의존성 주입 (DI) 적용, 느슨한 결합 원칙 준수
인터페이스 제약Target 인터페이스가 Adaptee 의 모든 기능을 감싸지 못할 수 있음다중 인터페이스 분리, Facade 패턴과 조합하여 세분화된 기능별 분리 설계
과도한 사용모든 호환 문제에 무조건 어댑터를 적용하면 오히려 설계가 비효율적이 될 수 있음필요성 검토 후 제한적 적용, 통합 전략과 설계 목적 명확화

문제점

항목원인영향탐지 및 진단예방 방법해결 방법 및 기법
메모리 누수Adapter 가 Adaptee 를 장기 참조하고 GC 대상이 되지 않음메모리 사용량 증가, 성능 저하Heap 분석, 메모리 프로파일러명시적 해제 로직 구현, 약한 참조 사용 (WeakReference)객체 수명 주기 관리, 자동 자원 해제 (contextlib, with)
순환 의존성Adapter 와 Adaptee 가 서로 참조하여 GC 되지 않는 순환 구조 발생메모리 누수, StackOverflow, 교착 가능성의존성 분석, 정적 분석 도구 사용방향성 있는 의존 설계, 중간 이벤트 버스나 중계자 사용이벤트 기반 구조 (Observer, Mediator) 로 전환
타입 안전성 부족동적 캐스팅 또는 비정형 객체 사용으로 인한 런타임 오류TypeError, ClassCastException, 기능 오작동정적 분석, 타입 검사기 (Mypy, TypeScript) 사용제네릭 및 타입 힌트 적용, 강타입 설계타입 가드 도입, 입력 검증 로직 추가
설정 불일치어댑터와 어댑티 간 설정 또는 인코딩 방식 등이 달라 발생실행 오류, 결과 왜곡, 예외 발생통합 테스트, 설정 유효성 검사 추가기본값 정의, 명시적 설정 명세 사용설정 매핑 테이블, 체크포인트 기반 설정 검증
동기화 문제멀티스레드 환경에서 어댑터 내부 공유 자원 접근 시 상태 불일치 발생데이터 손실, Race condition, 교착상태동시성 테스트, Thread Dump 분석불변 객체 사용, Thread-safe 구조 설계Lock, Queue, Atomic, Concurrent 자료구조 활용
다중 어댑터 관리 어려움다양한 외부 시스템을 연결할 때 어댑터 구성 및 유지가 복잡해짐유지보수 비용 증가, 시스템 확장성 저하구성 변경 시 테스트 실패 및 관리 혼란어댑터 등록/관리 메타데이터화, 명세 기반 관리 도입Adapter Factory 또는 Registry 패턴 도입
로직 중복여러 Adapter 에 유사한 변환 로직이 반복 구현됨유지보수성 악화, 코드 중복코드 리뷰, 중복 검사 도구 사용공통 인터페이스/변환 계층 도출Abstract Adapter 또는 유틸 기반 공통 모듈화
호환성 오류Adaptee 인터페이스 변경 후 Adapter 비호환 발생시스템 통합 실패, 예외 발생리그레션 테스트, 타입 시스템 적용변경 감지 테스트 도입, 계약 기반 설계 적용단위 테스트 강화, Wrapper 수정/리팩토링

도전 과제 및 해결책

카테고리도전 과제원인영향예방 방법해결 방안 및 기법
설계/구조다양한 외부 인터페이스 대응서로 다른 외부 시스템/라이브러리 연동 필요어댑터 수 증가, 구조 복잡화추상화 수준 재정의, 중간 변환 계층 도입, 공통 인터페이스 설계어댑터 팩토리/레지스트리 적용, 계층화 설계, 전략 어댑터 사용
상속 기반 어댑터 한계클래스 상속 기반은 언어 제한 존재 (ex. Java 다중 상속 불가)유연성 제한, 테스트 어려움상속보다 합성 (Composition) 우선객체 어댑터 방식 적용, 인터페이스 기반 설계로 전환
복잡성 관리어댑터 과다 사용무분별한 적용 또는 비즈니스 로직 포함유지보수성 저하, 코드 중복, 테스트 어려움SRP(단일 책임 원칙) 준수, 필요한 경우에만 도입, 설계 단계에서 명확한 기준 수립어댑터 전용 추상 계층 구성, 코드 생성 도구 사용, 문서화 강화
레거시 시스템 연동기존 시스템 전체 교체 어려움기술 부채 증가, 장애 복원력 저하점진적 마이그레이션 전략 (Strangler Fig Pattern), 경계 기반 리팩토링이벤트 소싱 + CQRS 기반 전환, 중간 어댑터 계층 분리
성능 최적화계층 오버헤드 및 데이터 변환 비용어댑터 계층 추가로 인한 호출/변환 오버헤드응답 지연, 처리량 감소, 리소스 과소 활용프로파일링 기반 설계 검토, 경량 구조 설계캐싱 적용, 비동기 처리 도입, Lazy Initialization 전략 도입
실시간 데이터 스트리밍 변환대용량 실시간 데이터 처리 및 포맷 변환 요구지연시간 증가, 실시간 처리 실패 가능성스트리밍 아키텍처 설계, 처리 방식 단순화Apache Kafka Streams / Flink 기반 변환 처리
테스트 전략어댑터 내부 호출의 테스트 어려움Adaptee 호출이 숨겨져 있어 테스트 불가능 또는 어려움테스트 커버리지 부족, 결함 검출 지연인터페이스 주입 (DI), 인터페이스 기반 설계Mock/Stub 도입, 테스트 더블 기반 단위 테스트 구성
다양한 어댑터 구성의 테스트 복잡도 증가시스템 통합이 많은 경우 각 어댑터별 테스트 환경 요구테스트 자동화 난이도 상승, 검증 누락 가능성테스트 계층 분리, 계약 기반 테스트 도입CI 파이프라인 연동, 시나리오 기반 테스트 자동화
운영/모니터링인터페이스 변경의 영향 파급외부 API 또는 라이브러리가 변경될 가능성 존재Adapter 실패, 시스템 장애인터페이스 변경 감지 도구 도입, 계약 기반 테스트 적용버전 명시 및 관리, 자동화 테스트 도입, 모의 (Mock) Adaptee 구성
장애 추적 어려움어댑터 계층 내부 처리가 로깅/트레이싱되지 않음원인 진단 지연, 장애 확산 가능성분산 트레이싱 (예: OpenTelemetry), 어댑터 내부 로깅 표준화 적용메트릭 기반 알림 설정, APM 연계, 구조적 로깅 적용
아키텍처 통합서비스 메시 환경에서 통신 복잡도 증가마이크로서비스 간 이기종 통신 방식 존재통신 비용 증가, 유지보수 어려움표준화된 API 계약 정의, 공통 프로토콜 지정API Gateway + Service Mesh 도입, Protocol Buffers 기반 통합

분류 기준에 따른 종류 및 유형

분류 기준종류/유형특징적용 상황
구현 방식Object Adapter합성 사용, 런타임 유연성단일 상속 언어, 다중 Adaptee 지원
구현 방식Class Adapter상속 사용, 컴파일타임 결정다중 상속 지원 언어, 양방향 변환
방향성단방향 Adapter한 방향으로만 변환API 통합, 데이터 형식 변환
방향성양방향 Adapter양방향 변환 지원시스템 간 상호 운용성
범위인터페이스 Adapter전체 인터페이스 변환완전한 시스템 통합
범위메서드 Adapter특정 메서드만 변환부분적 기능 통합
데이터 처리동기 Adapter즉시 처리 및 응답실시간 요구사항
데이터 처리비동기 Adapter지연 처리, 콜백 사용대용량 데이터, 네트워크 통신

실무 사용 예시

분야예시 / 적용 대상설명구현 방식
외부 API 연동결제 (PG), 인증, 메시징 API 어댑터서로 다른 외부 API 인터페이스를 내부 시스템에 맞게 통합객체 어댑터, 전략 어댑터 적용
레거시 시스템 통합OldService → NewService, 레거시 DB/서비스 래퍼기존 시스템을 신규 인터페이스에 맞게 마이그레이션 또는 연동추상 어댑터 + 어댑터 팩토리 조합
데이터 변환XML ↔ JSON, Kafka ↔ AWS SNS/SQS포맷 및 메시지 프로토콜 간 변환 수행메시지 어댑터, Serializer 어댑터
클라우드 및 플랫폼AWS SDK, Azure SDK, GCP 클라이언트멀티 클라우드 환경에서 벤더 종속성 제거 및 표준화플랫폼 추상화 어댑터
마이크로서비스 아키텍처API Gateway, gRPC ↔ REST마이크로서비스 간 통신 방식 변환 (REST ↔ gRPC 등)마이크로서비스용 네트워크 어댑터
로깅 시스템SLF4J ↔ Log4j, 외부 로깅 → 내부 Logger다양한 로깅 백엔드를 표준 인터페이스로 통합인터페이스 기반 어댑터
프론트엔드/모바일React Props 어댑터, RecyclerView Adapter비동기 응답 데이터를 UI 컴포넌트에 맞게 변환함수형 어댑터, 추상 클래스 상속
파일 및 스트림 처리InputStream → Reader, BufferedReader입출력 포맷이나 방식의 변환을 위한 래퍼 클래스 사용다중 레벨 어댑터
Java 표준 APIInputStreamReader, Collections.enumeration()자바 내장 어댑터 예시로, 객체 간 인터페이스 변환 수행내부 클래스, 표준 어댑터 구현
UI/그래픽 컴포넌트호환되지 않는 위젯 통합, 뷰 모델 매핑다양한 프레임워크 또는 디자인 시스템 간 UI 컴포넌트 연동컴포넌트 매핑 어댑터
데이터베이스 연동JDBC ↔ ORM (JPA), Connection Pool 어댑터DB 벤더 간 호환성 확보 및 마이그레이션 유연성 확보ORM 어댑터, 커넥션 추상화 계층
통합 메시징Event Bus ↔ MQ ↔ Kafka서로 다른 메시지 시스템 간의 메시지 포맷/전송 방식 통합메시지 브로커 어댑터
인증/보안 연동OAuth2, SAML, JWT 기반 IdP 어댑터다양한 인증 제공자를 하나의 인터페이스로 추상화인증 토큰 어댑터, 인터페이스 래핑

활용 사례

사례 1: 레거시 지불 시스템 연동

배경:

어댑터 구성:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Target
interface PaymentProcessor {
    void process(String amount);
}

// Adaptee
class LegacyPaymentService {
    void processOldPayment(double value) {
        System.out.println("Processed old payment: " + value);
    }
}

// Adapter
class PaymentAdapter implements PaymentProcessor {
    private LegacyPaymentService legacy = new LegacyPaymentService();

    public void process(String amount) {
        double value = Double.parseDouble(amount);
        legacy.processOldPayment(value);
    }
}

Workflow:

  1. 사용자는 PaymentProcessor 를 통해 결제 요청
  2. PaymentAdapter 가 요청을 변환
  3. LegacyPaymentService 가 처리

사례 2: 외부 메일 발송 솔루션 교체

시스템 구성:

Workflow:

  1. 기존 시스템은 Target 인터페이스만 사용
  2. MailSenderB 를 Adapter 로 감싸 Target 인터페이스로 변환
  3. 시스템 코드 수정 없이 신규 솔루션 적용
1
[Client] → [Target 인터페이스] ← [Adapter] → [MailSenderB(Adaptee)]

사례 3: 대형 전자상거래 플랫폼의 결제 시스템 통합

시스템 구성

  1. Frontend Application Layer

    • 사용자 인터페이스: React 기반 SPA
    • 장바구니 서비스: Node.js 기반 마이크로서비스
  2. Backend Services Layer

    • 결제 서비스: Spring Boot 기반 핵심 비즈니스 로직
    • 결제 어댑터: 다양한 PG 사 API 통합을 위한 Adapter Pattern 구현
  3. External Payment Gateways

    • 카드결제 PG: 신용카드/체크카드 처리
    • 간편결제 PG: 카카오페이, 네이버페이 등
    • 계좌이체 PG: 실시간 계좌이체
    • 은행 API: 직접 은행 연동
graph TB
    subgraph "Frontend Application"
        UI[사용자 인터페이스]
        Cart[장바구니 서비스]
    end
    
    subgraph "Backend Services"
        PaymentService[결제 서비스]
        PaymentAdapter[결제 어댑터]
    end
    
    subgraph "External Payment Gateways"
        PG1[카드결제 PG]
        PG2[간편결제 PG]
        PG3[계좌이체 PG]
        Bank[은행 API]
    end
    
    UI --> Cart
    Cart --> PaymentService
    PaymentService --> PaymentAdapter
    PaymentAdapter --> PG1
    PaymentAdapter --> PG2
    PaymentAdapter --> PG3
    PaymentAdapter --> Bank
    
    classDef frontend fill:#e1f5fe
    classDef backend fill:#f3e5f5
    classDef external fill:#fff3e0
    
    class UI,Cart frontend
    class PaymentService,PaymentAdapter backend
    class PG1,PG2,PG3,Bank external

Workflow:

sequenceDiagram
    participant User as 사용자
    participant UI as 프론트엔드
    participant PaymentService as 결제서비스
    participant PaymentAdapter as 결제어댑터
    participant PG as PG사

    User->>UI: 결제 요청
    UI->>PaymentService: 결제 처리 요청
    PaymentService->>PaymentService: 주문 검증
    PaymentService->>PaymentAdapter: 결제 실행
    PaymentAdapter->>PaymentAdapter: PG사 선택 및 데이터 변환
    PaymentAdapter->>PG: PG사별 API 호출
    PG->>PaymentAdapter: 결제 결과 응답
    PaymentAdapter->>PaymentAdapter: 응답 데이터 표준화
    PaymentAdapter->>PaymentService: 표준화된 결과 반환
    PaymentService->>PaymentService: 결제 결과 처리
    PaymentService->>UI: 결제 완료 응답
    UI->>User: 결제 결과 표시

Adapter Pattern 의 역할:

  1. 인터페이스 통합

    • 각 PG 사의 서로 다른 API 스펙을 표준 인터페이스로 통합
    • 결제 서비스는 하나의 인터페이스만 알면 모든 PG 사 이용 가능
  2. 데이터 변환

    • 주문 정보를 각 PG 사가 요구하는 형식으로 변환
    • 응답 데이터를 표준 형식으로 변환
  3. 오류 처리 표준화

    • 각 PG 사의 서로 다른 오류 코드를 표준 오류 형식으로 변환
    • 일관된 오류 처리 로직 제공
      PG 사마다 다른 API 제공. 시스템 내부는 BillingService 인터페이스만 사용함.

Adapter 구조:

classDiagram
    BillingService <|.. PG1Adapter
    BillingService <|.. PG2Adapter
    PG1Adapter --> PG1API
    PG2Adapter --> PG2API
    Client --> BillingService

Adapter 유무에 따른 차이점

구현 예시

Javascript

 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
// JavaScript 예제
class OldPrinter {
    printOld(text) {
        console.log(`Old format: ${text}`);
    }
}

class NewPrinter {
    print(text) {
        console.log(`New format: ${text}`);
    }
}

// Adapter
class PrinterAdapter {
    constructor(oldPrinter) {
        this.oldPrinter = oldPrinter;
    }
    
    print(text) {
        this.oldPrinter.printOld(text);
    }
}

// 사용법
const oldPrinter = new OldPrinter();
const adapter = new PrinterAdapter(oldPrinter);
adapter.print("Hello World"); // Old format: Hello World

JavaScript: 내부 결제 시스템

 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
// Adaptee: 외부 결제 API
class ExternalPaymentAPI {
  pay(amount, cardInfo) {
    console.log(`외부 결제 API로 ${amount}원 결제 요청: ${cardInfo}`);
    return { success: true, transactionId: "12345" };
  }
}

// Target: 내부 결제 시스템이 기대하는 인터페이스
class PaymentTarget {
  request(amount, userInfo) {
    throw new Error("이 메서드는 반드시 구현되어야 합니다.");
  }
}

// Adapter: 외부 API를 내부 시스템에 맞게 변환
class PaymentAdapter extends PaymentTarget {
  constructor(externalApi) {
    super();
    this.externalApi = externalApi;
  }

  request(amount, userInfo) {
    // 내부 시스템의 userInfo를 외부 API의 cardInfo로 변환
    const cardInfo = `${userInfo.name}의 카드`;
    return this.externalApi.pay(amount, cardInfo);
  }
}

// Client: 내부 결제 시스템
const externalApi = new ExternalPaymentAPI();
const adapter = new PaymentAdapter(externalApi);
const result = adapter.request(10000, { name: "홍길동" });
console.log(result);

Python

 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
# Target Interface
class BillingService:
    def pay(self, amount: float) -> str:
        raise NotImplementedError()

# Adaptee
class PG1API:
    def make_payment(self, value):
        return f"PG1 결제 완료: {value}"

# Adapter
class PG1Adapter(BillingService):
    def __init__(self):
        self.pg1 = PG1API()

    def pay(self, amount: float) -> str:
        # 내부 Adaptee 호출로 변환
        return self.pg1.make_payment(amount)

# Client
def checkout(billing_service: BillingService):
    print(billing_service.pay(100.0))

# 실행
checkout(PG1Adapter())

이 코드는 클라이언트는 BillingService 만 알면 되고, PG API 변경은 PG1Adapter 만 수정하면 되는 구조로, Adapter 패턴의 구조적 이점을 잘 보여준다.

Python: 결제 서비스

  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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
from enum import Enum
import json
import uuid
from datetime import datetime

# 결제 상태 열거형
class PaymentStatus(Enum):
    PENDING = "pending"
    SUCCESS = "success"
    FAILED = "failed"
    CANCELLED = "cancelled"

# 결제 결과 데이터 클래스
class PaymentResult:
    def __init__(self, status: PaymentStatus, transaction_id: str, 
                 message: str = "", amount: float = 0.0):
        self.status = status
        self.transaction_id = transaction_id
        self.message = message
        self.amount = amount
        self.timestamp = datetime.now()
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "status": self.status.value,
            "transaction_id": self.transaction_id,
            "message": self.message,
            "amount": self.amount,
            "timestamp": self.timestamp.isoformat()
        }

# Target 인터페이스 - 표준 결제 처리 인터페이스
class PaymentProcessor(ABC):
    """표준 결제 처리 인터페이스"""
    
    @abstractmethod
    def process_payment(self, amount: float, currency: str, 
                       payment_method: str, user_id: str) -> PaymentResult:
        """결제 처리"""
        pass
    
    @abstractmethod
    def verify_payment(self, transaction_id: str) -> PaymentResult:
        """결제 검증"""
        pass
    
    @abstractmethod
    def cancel_payment(self, transaction_id: str) -> PaymentResult:
        """결제 취소"""
        pass

# Adaptee 1 - 카드 결제 PG사 (기존 레거시 시스템)
class CardPaymentGateway:
    """카드 결제 PG사 레거시 시스템"""
    
    def __init__(self, merchant_id: str):
        self.merchant_id = merchant_id
        self.transactions = {}  # 거래 저장소
    
    def charge_card(self, card_info: Dict, amount_cents: int, 
                   currency_code: str) -> Dict[str, Any]:
        """카드 결제 처리 (레거시 인터페이스)"""
        print(f"[CardPG] Processing card payment: {amount_cents} cents")
        
        # 결제 처리 시뮬레이션
        transaction_ref = f"CARD_{uuid.uuid4().hex[:8].upper()}"
        
        # 간단한 검증 로직
        if amount_cents <= 0:
            return {
                "result_code": "FAIL",
                "error_message": "Invalid amount",
                "tx_ref": transaction_ref
            }
        
        # 성공 처리
        transaction_data = {
            "amount_cents": amount_cents,
            "currency": currency_code,
            "card_info": card_info,
            "timestamp": datetime.now()
        }
        self.transactions[transaction_ref] = transaction_data
        
        return {
            "result_code": "SUCCESS",
            "tx_ref": transaction_ref,
            "approval_number": f"APP{uuid.uuid4().hex[:6].upper()}"
        }
    
    def check_transaction(self, tx_ref: str) -> Dict[str, Any]:
        """거래 조회 (레거시 인터페이스)"""
        if tx_ref in self.transactions:
            return {
                "status": "APPROVED",
                "tx_ref": tx_ref,
                "transaction": self.transactions[tx_ref]
            }
        return {"status": "NOT_FOUND", "tx_ref": tx_ref}
    
    def void_transaction(self, tx_ref: str) -> Dict[str, Any]:
        """거래 취소 (레거시 인터페이스)"""
        if tx_ref in self.transactions:
            del self.transactions[tx_ref]
            return {"void_status": "COMPLETED", "tx_ref": tx_ref}
        return {"void_status": "FAILED", "error": "Transaction not found"}

# Adaptee 2 - 간편 결제 PG사
class SimplePaymentAPI:
    """간편 결제 PG사 API"""
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.payments = {}
    
    def create_payment(self, payment_request: Dict) -> Dict[str, Any]:
        """간편 결제 생성"""
        print(f"[SimplePay] Creating payment: {payment_request}")
        
        payment_id = f"SP_{uuid.uuid4().hex[:10].upper()}"
        
        if payment_request.get("amount", 0) <= 0:
            return {
                "success": False,
                "error_code": "INVALID_AMOUNT",
                "payment_id": payment_id
            }
        
        self.payments[payment_id] = {
            "amount": payment_request["amount"],
            "currency": payment_request.get("currency", "KRW"),
            "user_info": payment_request.get("user_info", {}),
            "status": "COMPLETED"
        }
        
        return {
            "success": True,
            "payment_id": payment_id,
            "status": "COMPLETED"
        }
    
    def get_payment_info(self, payment_id: str) -> Dict[str, Any]:
        """결제 정보 조회"""
        if payment_id in self.payments:
            return {
                "found": True,
                "payment_data": self.payments[payment_id]
            }
        return {"found": False}
    
    def refund_payment(self, payment_id: str) -> Dict[str, Any]:
        """결제 환불"""
        if payment_id in self.payments:
            self.payments[payment_id]["status"] = "REFUNDED"
            return {"refund_success": True, "payment_id": payment_id}
        return {"refund_success": False, "error": "Payment not found"}

# Adapter 1 - 카드 결제 어댑터
class CardPaymentAdapter(PaymentProcessor):
    """카드 결제 PG를 표준 인터페이스로 변환하는 어댑터"""
    
    def __init__(self, card_gateway: CardPaymentGateway):
        self.card_gateway = card_gateway
        self.currency_map = {"USD": "USD", "KRW": "KRW", "EUR": "EUR"}
    
    def process_payment(self, amount: float, currency: str, 
                       payment_method: str, user_id: str) -> PaymentResult:
        """표준 결제 인터페이스를 카드 PG 호출로 변환"""
        try:
            # 데이터 변환: float -> int (cents)
            amount_cents = int(amount * 100)
            currency_code = self.currency_map.get(currency, "KRW")
            
            # 카드 정보 구성 (실제로는 암호화된 토큰 사용)
            card_info = {
                "user_id": user_id,
                "payment_method": payment_method
            }
            
            # 레거시 PG 호출
            result = self.card_gateway.charge_card(
                card_info, amount_cents, currency_code
            )
            
            # 결과 변환
            if result["result_code"] == "SUCCESS":
                return PaymentResult(
                    status=PaymentStatus.SUCCESS,
                    transaction_id=result["tx_ref"],
                    message=f"Card payment successful. Approval: {result['approval_number']}",
                    amount=amount
                )
            else:
                return PaymentResult(
                    status=PaymentStatus.FAILED,
                    transaction_id=result["tx_ref"],
                    message=result.get("error_message", "Payment failed")
                )
                
        except Exception as e:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=f"ERROR_{uuid.uuid4().hex[:8]}",
                message=f"Payment processing error: {str(e)}"
            )
    
    def verify_payment(self, transaction_id: str) -> PaymentResult:
        """거래 검증"""
        try:
            result = self.card_gateway.check_transaction(transaction_id)
            
            if result["status"] == "APPROVED":
                transaction = result["transaction"]
                return PaymentResult(
                    status=PaymentStatus.SUCCESS,
                    transaction_id=transaction_id,
                    message="Payment verified",
                    amount=transaction["amount_cents"] / 100.0
                )
            else:
                return PaymentResult(
                    status=PaymentStatus.FAILED,
                    transaction_id=transaction_id,
                    message="Payment not found or failed"
                )
                
        except Exception as e:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=transaction_id,
                message=f"Verification error: {str(e)}"
            )
    
    def cancel_payment(self, transaction_id: str) -> PaymentResult:
        """결제 취소"""
        try:
            result = self.card_gateway.void_transaction(transaction_id)
            
            if result["void_status"] == "COMPLETED":
                return PaymentResult(
                    status=PaymentStatus.CANCELLED,
                    transaction_id=transaction_id,
                    message="Payment cancelled successfully"
                )
            else:
                return PaymentResult(
                    status=PaymentStatus.FAILED,
                    transaction_id=transaction_id,
                    message=result.get("error", "Cancellation failed")
                )
                
        except Exception as e:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=transaction_id,
                message=f"Cancellation error: {str(e)}"
            )

# Adapter 2 - 간편 결제 어댑터
class SimplePaymentAdapter(PaymentProcessor):
    """간편 결제를 표준 인터페이스로 변환하는 어댑터"""
    
    def __init__(self, simple_pay_api: SimplePaymentAPI):
        self.simple_pay_api = simple_pay_api
    
    def process_payment(self, amount: float, currency: str, 
                       payment_method: str, user_id: str) -> PaymentResult:
        """표준 결제 인터페이스를 간편 결제 API 호출로 변환"""
        try:
            # 요청 데이터 구성
            payment_request = {
                "amount": amount,
                "currency": currency,
                "user_info": {
                    "user_id": user_id,
                    "payment_method": payment_method
                }
            }
            
            # 간편 결제 API 호출
            result = self.simple_pay_api.create_payment(payment_request)
            
            # 결과 변환
            if result["success"]:
                return PaymentResult(
                    status=PaymentStatus.SUCCESS,
                    transaction_id=result["payment_id"],
                    message="Simple payment completed",
                    amount=amount
                )
            else:
                return PaymentResult(
                    status=PaymentStatus.FAILED,
                    transaction_id=result["payment_id"],
                    message=f"Payment failed: {result.get('error_code', 'Unknown error')}"
                )
                
        except Exception as e:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=f"ERROR_{uuid.uuid4().hex[:8]}",
                message=f"Simple payment error: {str(e)}"
            )
    
    def verify_payment(self, transaction_id: str) -> PaymentResult:
        """결제 검증"""
        try:
            result = self.simple_pay_api.get_payment_info(transaction_id)
            
            if result["found"]:
                payment_data = result["payment_data"]
                status = PaymentStatus.SUCCESS if payment_data["status"] == "COMPLETED" else PaymentStatus.FAILED
                
                return PaymentResult(
                    status=status,
                    transaction_id=transaction_id,
                    message=f"Payment status: {payment_data['status']}",
                    amount=payment_data["amount"]
                )
            else:
                return PaymentResult(
                    status=PaymentStatus.FAILED,
                    transaction_id=transaction_id,
                    message="Payment not found"
                )
                
        except Exception as e:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=transaction_id,
                message=f"Verification error: {str(e)}"
            )
    
    def cancel_payment(self, transaction_id: str) -> PaymentResult:
        """결제 취소"""
        try:
            result = self.simple_pay_api.refund_payment(transaction_id)
            
            if result["refund_success"]:
                return PaymentResult(
                    status=PaymentStatus.CANCELLED,
                    transaction_id=transaction_id,
                    message="Payment refunded successfully"
                )
            else:
                return PaymentResult(
                    status=PaymentStatus.FAILED,
                    transaction_id=transaction_id,
                    message=result.get("error", "Refund failed")
                )
                
        except Exception as e:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=transaction_id,
                message=f"Refund error: {str(e)}"
            )

# 결제 서비스 (Client)
class PaymentService:
    """통합 결제 서비스 - Adapter들을 활용하는 클라이언트"""
    
    def __init__(self):
        # 여러 결제 수단을 위한 어댑터들
        self.payment_adapters: Dict[str, PaymentProcessor] = {}
        
        # 레거시 PG 시스템들 초기화
        card_gateway = CardPaymentGateway("MERCHANT_123")
        simple_pay_api = SimplePaymentAPI("API_KEY_456")
        
        # 어댑터들 등록
        self.payment_adapters["card"] = CardPaymentAdapter(card_gateway)
        self.payment_adapters["simplepay"] = SimplePaymentAdapter(simple_pay_api)
    
    def add_payment_method(self, method_name: str, adapter: PaymentProcessor):
        """새로운 결제 수단 추가"""
        self.payment_adapters[method_name] = adapter
        print(f"Payment method '{method_name}' added successfully")
    
    def process_payment(self, payment_method: str, amount: float, 
                       currency: str, user_id: str) -> PaymentResult:
        """통합 결제 처리"""
        print(f"\n=== 결제 처리 시작 ===")
        print(f"결제 수단: {payment_method}")
        print(f"금액: {amount} {currency}")
        print(f"사용자 ID: {user_id}")
        
        # 결제 수단별 어댑터 선택
        if payment_method not in self.payment_adapters:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=f"ERROR_{uuid.uuid4().hex[:8]}",
                message=f"Unsupported payment method: {payment_method}"
            )
        
        # 선택된 어댑터를 통해 결제 처리
        adapter = self.payment_adapters[payment_method]
        result = adapter.process_payment(amount, currency, payment_method, user_id)
        
        print(f"결제 결과: {result.status.value}")
        print(f"거래 ID: {result.transaction_id}")
        print(f"메시지: {result.message}")
        print(f"=== 결제 처리 완료 ===\n")
        
        return result
    
    def verify_payment(self, payment_method: str, transaction_id: str) -> PaymentResult:
        """결제 검증"""
        if payment_method not in self.payment_adapters:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=transaction_id,
                message=f"Unsupported payment method: {payment_method}"
            )
        
        adapter = self.payment_adapters[payment_method]
        return adapter.verify_payment(transaction_id)
    
    def cancel_payment(self, payment_method: str, transaction_id: str) -> PaymentResult:
        """결제 취소"""
        if payment_method not in self.payment_adapters:
            return PaymentResult(
                status=PaymentStatus.FAILED,
                transaction_id=transaction_id,
                message=f"Unsupported payment method: {payment_method}"
            )
        
        adapter = self.payment_adapters[payment_method]
        return adapter.cancel_payment(transaction_id)

# 사용 예시 및 테스트
def demonstrate_adapter_pattern():
    """Adapter Pattern 시연"""
    print("=== Adapter Pattern 활용 사례: 통합 결제 시스템 ===\n")
    
    # 결제 서비스 초기화
    payment_service = PaymentService()
    
    # 다양한 결제 수단으로 결제 처리
    test_cases = [
        ("card", 100.00, "USD", "user_001"),
        ("simplepay", 50000.0, "KRW", "user_002"),
        ("invalid_method", 25.0, "EUR", "user_003")  # 지원하지 않는 결제 수단
    ]
    
    results = []
    
    for payment_method, amount, currency, user_id in test_cases:
        result = payment_service.process_payment(payment_method, amount, currency, user_id)
        results.append((payment_method, result))
        
        # 성공한 결제에 대해 검증 및 취소 테스트
        if result.status == PaymentStatus.SUCCESS:
            print(f"--- {payment_method} 결제 검증 ---")
            verify_result = payment_service.verify_payment(payment_method, result.transaction_id)
            print(f"검증 결과: {verify_result.status.value} - {verify_result.message}")
            
            print(f"--- {payment_method} 결제 취소 ---")
            cancel_result = payment_service.cancel_payment(payment_method, result.transaction_id)
            print(f"취소 결과: {cancel_result.status.value} - {cancel_result.message}\n")
    
    # 결과 요약
    print("=== 결제 처리 결과 요약 ===")
    for payment_method, result in results:
        print(f"{payment_method}: {result.status.value} - {result.message}")
    
    # 새로운 결제 수단 추가 시연
    print("\n=== 새 결제 수단 추가 시연 ===")
    
    # 가상의 새 PG사 어댑터 (간단한 더미 구현)
    class NewPaymentAdapter(PaymentProcessor):
        def process_payment(self, amount, currency, payment_method, user_id):
            return PaymentResult(
                status=PaymentStatus.SUCCESS,
                transaction_id=f"NEW_{uuid.uuid4().hex[:8]}",
                message="New payment method success",
                amount=amount
            )
        
        def verify_payment(self, transaction_id):
            return PaymentResult(status=PaymentStatus.SUCCESS, transaction_id=transaction_id)
        
        def cancel_payment(self, transaction_id):
            return PaymentResult(status=PaymentStatus.CANCELLED, transaction_id=transaction_id)
    
    # 새 결제 수단 추가
    payment_service.add_payment_method("newpay", NewPaymentAdapter())
    
    # 새 결제 수단으로 결제 테스트
    new_result = payment_service.process_payment("newpay", 75.0, "USD", "user_004")
    print(f"새 결제 수단 결과: {new_result.status.value} - {new_result.message}")

# 실행
if __name__ == "__main__":
    demonstrate_adapter_pattern()

Java

 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
// Target 인터페이스
public interface MediaPlayer {
   void play(String audioType, String fileName);
}

// Adaptee 클래스들
public class Mp4Player {
   public void playMp4(String fileName) {
       System.out.println("Playing mp4 file: " + fileName);
   }
}

public class VlcPlayer {
   public void playVlc(String fileName) {
       System.out.println("Playing vlc file: " + fileName);
   }
}

// Adapter 클래스
public class MediaAdapter implements MediaPlayer {
   private Mp4Player mp4Player;
   private VlcPlayer vlcPlayer;
   
   public MediaAdapter(String audioType) {
       if(audioType.equalsIgnoreCase("mp4")) {
           mp4Player = new Mp4Player();
       } else if(audioType.equalsIgnoreCase("vlc")) {
           vlcPlayer = new VlcPlayer();
       }
   }
   
   @Override
   public void play(String audioType, String fileName) {
       if(audioType.equalsIgnoreCase("mp4")) {
           mp4Player.playMp4(fileName);
       } else if(audioType.equalsIgnoreCase("vlc")) {
           vlcPlayer.playVlc(fileName);
       }
   }
}

Java: GraphQL → REST Adapter

기존 REST API(GET /users/{id}) 를 사용하는 시스템을 GraphQL API 를 통해 통합 제공해야 함.

  1. GraphQL 스키마 정의

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    type Query {
      user(id: ID!): User
    }
    
    type User {
      id: ID!
      name: String
      email: String
    }
    
  2. 구성도

    1
    2
    3
    4
    5
    6
    7
    
    Client (GraphQL)
    GraphQL Resolver (user)
    REST 호출 (GET /users/{id})
    REST 응답 객체 → Adapter → GraphQL DTO
    
  3. REST API 응답 예시

    1
    2
    3
    4
    5
    6
    
    GET /api/v1/users/42
    {
      "userId": 42,
      "username": "lee",
      "emailAddress": "lee@example.com"
    }
    
  4. Java (Spring Boot + GraphQL) 어댑터 구현

    1
    2
    3
    4
    5
    6
    7
    8
    
    // GraphQL의 User 타입
    public class UserDTO {
        private String id;
        private String name;
        private String email;
    
        // getters/setters
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    
    // REST 응답 모델
    public class RestUserResponse {
        private int userId;
        private String username;
        private String emailAddress;
    
        // getters/setters
    }
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    // 어댑터: REST API → GraphQL DTO 변환
    @Component
    public class UserAdapter {
    
        public UserDTO fromRest(RestUserResponse restUser) {
            UserDTO dto = new UserDTO();
            dto.setId(String.valueOf(restUser.getUserId()));
            dto.setName(restUser.getUsername());
            dto.setEmail(restUser.getEmailAddress());
            return dto;
        }
    }
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    // GraphQL Resolver
    @Component
    public class UserQueryResolver implements GraphQLQueryResolver {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private UserAdapter adapter;
    
        public UserDTO user(Long id) {
            String url = "http://internal-service/api/v1/users/" + id;
            RestUserResponse restUser = restTemplate.getForObject(url, RestUserResponse.class);
            return adapter.fromRest(restUser);
        }
    }
    

Adapter vs. Facade vs. Decorator

구분Adapter (어댑터)Facade (퍼사드)Decorator (데코레이터)
목적인터페이스 호환복잡한 서브시스템을 단순화객체 기능 확장
구조클라이언트 ↔ Adapter ↔ Adaptee클라이언트 ↔ Facade ↔ 복잡한 하위 컴포넌트객체 ↔ 여러 데코레이터 체인
동작요청 변환 후 기존 객체에 위임여러 객체를 묶어 하나의 API 처럼 제공기존 객체에 기능을 동적으로 추가
사용 사례레거시 코드 연동, 호환성 해결서브시스템 단순화, 외부 API 감싸기기능 추가 (로깅, 캐싱 등)
변경 여부인터페이스 변경 없이 사용내부 구현 감추고 단순화기존 클래스를 수정하지 않음
예시SLF4J ↔ Log4j 어댑터Spring RestTemplate 내부 구현Java IO 의 BufferedReader, GzipInputStream

공통 주제: Audio Player

  • 다양한 오디오 형식 (mp3, wav 등) 을 재생
  • Adapter: 기존 클래스 호환을 위해
  • Facade: 복잡한 하위 시스템 단순화
  • Decorator: 기능을 확장 (예: 이펙트, 로그, 필터)
항목AdapterFacadeDecorator
목적기존 클래스를 호환 가능한 인터페이스로 변환복잡한 서브시스템을 단순한 API 로 감쌈기능을 동적으로 추가, 확장
구조Adaptee → Adapter → Target여러 하위 시스템 → Facade → 사용자Component → Decorator → 확장 기능
적용 대상레거시 시스템 또는 외부 API라이브러리, 프레임워크 초기화 또는 멀티 구성 단계기능 확장이 필요한 모듈형 시스템
기능 추가❌ (기능 변경 없음)❌ (단순화만, 기능 추가 없음)✅ (기능 확장에 적합, 체이닝 가능)
예시 키워드호환성, 포맷 변환, 레거시 래핑간편 API, 복잡도 은닉, 사용성 향상로그, 이펙트, 필터, 권한 체크 등 기능 체이닝
Adapter Pattern

기존 클래스의 인터페이스를 변경 없이 다른 인터페이스에 맞춰주는 패턴
→ 예전 라이브러리를 최신 시스템에 맞춰 사용

코드 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 기존에 존재하던 클래스를 가정 (Adaptee)
class LegacyWavPlayer:
    def play_wav_file(self, filename: str):
        print(f"[Legacy WAV] Playing {filename}")

# 클라이언트가 기대하는 인터페이스
class AudioPlayer:
    def play(self, filename: str):
        raise NotImplementedError()

# Adapter: WAV를 AudioPlayer에 맞춰줌
class WavAdapter(AudioPlayer):
    def __init__(self, legacy_player: LegacyWavPlayer):
        self.legacy = legacy_player

    def play(self, filename: str):
        self.legacy.play_wav_file(filename)

# 사용 예시
adapter = WavAdapter(LegacyWavPlayer())
adapter.play("sound.wav")
Facade Pattern

복잡한 서브 시스템을 단순화된 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
# 복잡한 하위 컴포넌트들
class Decoder:
    def decode(self, filename: str):
        print(f"Decoding {filename}")

class AudioOutput:
    def setup(self):
        print("Setting up audio output")

    def play_audio(self):
        print("Playing audio")

class Equalizer:
    def apply(self):
        print("Applying default EQ")

# Facade 클래스
class SimpleAudioPlayer:
    def __init__(self):
        self.decoder = Decoder()
        self.output = AudioOutput()
        self.eq = Equalizer()

    def play(self, filename: str):
        self.decoder.decode(filename)
        self.eq.apply()
        self.output.setup()
        self.output.play_audio()

# 사용 예시
player = SimpleAudioPlayer()
player.play("song.mp3")
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
28
29
30
31
32
33
# 기본 인터페이스
class AudioPlayer:
    def play(self, filename: str):
        raise NotImplementedError()

# 기본 구현
class BasicAudioPlayer(AudioPlayer):
    def play(self, filename: str):
        print(f"Playing {filename}")

# 데코레이터 베이스 클래스
class AudioPlayerDecorator(AudioPlayer):
    def __init__(self, player: AudioPlayer):
        self.player = player

    def play(self, filename: str):
        self.player.play(filename)

# 효과 추가 데코레이터
class ReverbEffect(AudioPlayerDecorator):
    def play(self, filename: str):
        print("Applying Reverb Effect")
        super().play(filename)

# 로그 추가 데코레이터
class LoggingDecorator(AudioPlayerDecorator):
    def play(self, filename: str):
        print(f"[Log] Start playing: {filename}")
        super().play(filename)

# 사용 예시
player = LoggingDecorator(ReverbEffect(BasicAudioPlayer()))
player.play("music.mp3")

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

카테고리고려사항설명권장사항
설계인터페이스 명세 정의클라이언트 요구와 Target 인터페이스의 계약 정의 필요Target 인터페이스 우선 설계, 명확한 명세 문서화, 도메인 전문가와 협업 필요
클래스 vs 객체 어댑터 선택언어 특성에 따라 클래스 어댑터 (상속) 사용이 제한될 수 있음객체 어댑터 (합성) 방식 우선 적용, 특히 Java 등 다중 상속 제한 언어에서 추천
책임 분리어댑터에 도메인 로직이 섞이지 않도록 설계 필요로직 최소화, SRP(단일 책임 원칙) 준수
인터페이스 일관성다양한 Adaptee 관리 시 인터페이스 일관성 필요공통 Target 인터페이스 정의, 표준화된 계약 사용
중복 방지다양한 시스템과 연동 시 어댑터 계층이 중복될 수 있음추상 어댑터/팩토리 패턴 도입, 공통화 구조 설계
성능변환 오버헤드어댑터 계층에서의 데이터 변환 비용 존재병목 구간 프로파일링, 캐싱 및 배치 처리 적용
계층 오버헤드계층 과도화로 인한 응답 시간 증가계층 최소화 또는 직접 통합 검토, 경량 어댑터 설계
성능 테스트 필요성고빈도 호출 시 어댑터 성능 영향 확인 필수성능 프로파일링 수행, 시나리오 기반 부하 테스트
테스트단위 테스트 가능성Adaptee 내부 호출 시 테스트 곤란 가능성 존재DI 기반 설계, Mock 또는 Stub 활용, 어댑터 단위 테스트 강화
통합 테스트 복잡도다수 시스템과의 연동으로 테스트 복잡도 증가테스트 환경 분리, 단계별 시나리오 테스트, 테스트 자동화 도구 활용
운영예외 처리Adaptee 의 예외를 클라이언트에 맞게 매핑 필요의미 있는 예외 메시지 변환, Target 인터페이스의 표준 에러 설계 적용
모니터링 및 추적장애 추적과 성능 모니터링이 어려울 수 있음분산 트레이싱 (OpenTelemetry 등) 도입, 메트릭 기반 알림 구성
유지보수버전 호환성Adaptee 변경 시 Target 과의 호환성 고려 필요계약 테스트 적용, 버전 명시 및 점진적 롤아웃 전략 적용
복잡성 증가어댑터 수 증가 시 유지보수 난이도 증가모듈화, 어댑터 레지스트리 적용, 코드 생성 도구 활용
문서화어댑터 내부 로직과 변환 기준이 불명확할 수 있음변환 규칙과 예외 처리 기준을 포함한 명세 문서 작성
보안민감 데이터 처리인증 정보 또는 개인정보 등 처리 시 주의 필요암호화, 토큰화 적용 및 보안 감사 로그 구축

테스트 전략

  1. 단위 테스트 전략

    • 목표: CardPaymentAdapter 클래스의 동작을 외부 시스템의 영향 없이 독립적으로 검증
    • 대상: 어댑터 계층이 올바르게 요청을 전달하고, 응답을 변환하며, 예외를 처리하는지 확인
    • 접근법: 외부 의존성 (CardPaymentGateway) 을 Mock으로 대체하여 어댑터의 책임만 테스트
     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
    
    import unittest
    from unittest.mock import Mock, MagicMock
    
    class TestPaymentAdapter(unittest.TestCase):
        """결제 어댑터 단위 테스트"""
    
        def setUp(self):
            self.mock_gateway = Mock(spec=CardPaymentGateway)
            self.adapter = CardPaymentAdapter(self.mock_gateway)
    
        def test_successful_payment(self):
            """성공적인 결제 테스트
               성공 케이스를 Mock 응답으로 설정하고, 어댑터가 `SUCCESS` 상태를 반환하는지 검증"""
            # Given
            self.mock_gateway.charge_card.return_value = {
                "result_code": "SUCCESS",
                "tx_ref": "TX_12345",
                "approval_number": "APP_67890"
            }
    
            # When
            result = self.adapter.process_payment(100.0, "USD", "card", "user_001")
    
            # Then
            self.assertEqual(result.status, PaymentStatus.SUCCESS)
            self.assertEqual(result.transaction_id, "TX_12345")
            self.mock_gateway.charge_card.assert_called_once()
    
        def test_failed_payment(self):
            """실패한 결제 테스트
               결제 실패 응답 시 어댑터가 적절한 실패 상태 및 메시지를 반환하는지 검증
            """
            # Given
            self.mock_gateway.charge_card.return_value = {
                "result_code": "FAIL",
                "error_message": "Insufficient funds",
                "tx_ref": "TX_12346"
            }
    
            # When
            result = self.adapter.process_payment(100.0, "USD", "card", "user_001")
    
            # Then
            self.assertEqual(result.status, PaymentStatus.FAILED)
            self.assertIn("Insufficient funds", result.message)
    
        def test_exception_handling(self):
            """예외 처리 테스트
               예외 발생 시 어댑터가 **Fail-safe** 방식으로 예외를 처리하고, 실패 상태로 응답하는지 확인
            """
            # Given
            self.mock_gateway.charge_card.side_effect = Exception("Network error")
    
            # When
            result = self.adapter.process_payment(100.0, "USD", "card", "user_001")
    
            # Then
            self.assertEqual(result.status, PaymentStatus.FAILED)
            self.assertIn("Network error", result.message)
    
  2. 통합 테스트 전략

    • 목표: 어댑터와 실제 비즈니스 로직, 백엔드 시스템 간의 전체 연동 흐름을 검증
    • 대상: PaymentService 전체 흐름 (process, verify, cancel) 이 일관성 있게 동작하는지 확인
    • 접근법: 어댑터가 연결된 실제 서비스 또는 Stub 을 통해 종단 간 흐름을 검증
     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
    
    class TestPaymentIntegration(unittest.TestCase):
        """결제 시스템 통합 테스트"""
    
        def setUp(self):
            self.payment_service = PaymentService()
    
        def test_end_to_end_payment_flow(self):
            """전체 결제 플로우 테스트"""
            # Given
            payment_method = "card"
            amount = 100.0
            currency = "USD"
            user_id = "test_user"
    
            # When - 결제 처리
            # `process_payment()` 호출 → 어댑터 호출 및 결제 요청 전달
            payment_result = self.payment_service.process_payment(
                payment_method, amount, currency, user_id
            )
    
            # Then - 결제 성공 확인
            self.assertEqual(payment_result.status, PaymentStatus.SUCCESS)
            transaction_id = payment_result.transaction_id
    
            # When - 결제 검증
            # `verify_payment()` 호출 → 트랜잭션 ID 기반 상태 확인
            verify_result = self.payment_service.verify_payment(
                payment_method, transaction_id
            )
    
            # Then - 검증 성공 확인
            self.assertEqual(verify_result.status, PaymentStatus.SUCCESS)
    
            # When - 결제 취소
            # `cancel_payment()` 호출 → 결제 취소 로직 검증
            cancel_result = self.payment_service.cancel_payment(
                payment_method, transaction_id
            )
    
            # Then - 취소 성공 확인
            self.assertEqual(cancel_result.status, PaymentStatus.CANCELLED)
    
  3. 성능 테스트 전략

    • 목표: 다수의 동시 요청에 대해 어댑터 및 결제 서비스가 지속적으로 안정적인 성능을 제공하는지 검증
    • 대상: PaymentService응답 시간, 성공률, 동시성 처리 능력
    • 접근법: ThreadPoolExecutor병렬 요청을 시뮬레이션하여 부하 상황을 재현
     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
    
    import time
    import threading
    from concurrent.futures import ThreadPoolExecutor
    
    class TestPaymentPerformance(unittest.TestCase):
        """결제 시스템 성능 테스트"""
    
        def test_concurrent_payments(self):
            """동시 결제 처리 성능 테스트"""
            payment_service = PaymentService()
            # 동시에 전송할 요청 수 (100건 등)
            concurrent_requests = 100
    	    # max_workers: 실제 동시 스레드 수 (10개 스레드로 분산 실행)
            def process_payment():
                return payment_service.process_payment(
                    "card", 10.0, "USD", f"user_{threading.current_thread().ident}"
                )
    
            start_time = time.time()
    
            with ThreadPoolExecutor(max_workers=10) as executor:
                futures = [executor.submit(process_payment) for _ in range(concurrent_requests)]
                results = [future.result() for future in futures]
    
            end_time = time.time()
            processing_time = end_time - start_time
    
            # 성능 검증
            # 전체 요청을 5초 이내에 처리할 수 있는지 측정 → 성능 기준치 설정
            self.assertLess(processing_time, 5.0)  # 5초 이내 처리
            success_count = sum(1 for r in results if r.status == PaymentStatus.SUCCESS)
            self.assertGreaterEqual(success_count / concurrent_requests, 0.95)  # 95% 이상 성공률
    

리팩토링 전략

  1. 레거시 코드 점진적 리팩토링

    • 목표: 전체 시스템을 한 번에 바꾸는 것이 어려울 때, 기존 시스템 (Legacy)새 시스템 (Modern) 을 공존시키며 점진적으로 전환
    • 전략: 요청 트래픽을 일정 비율로 모던 시스템에 분산 라우팅하여 리스크를 줄이는 방식 (Strangler Fig 패턴 기반)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    class LegacyRefactoringStrategy:
        """레거시 시스템 점진적 리팩토링 전략"""
    
        def __init__(self, legacy_system, modern_interface):
            self.legacy_system = legacy_system
            self.modern_interface = modern_interface
            self.migration_percentage = 0.0
    
        def migrate_traffic(self, percentage: float):
            """트래픽 점진적 마이그레이션"""
            self.migration_percentage = min(percentage, 100.0)
    
        def process_request(self, request):
            """요청을 레거시/모던 시스템으로 라우팅"""
            import random
    
            if random.random() * 100 < self.migration_percentage:
                # 모던 시스템으로 라우팅
                return self.modern_interface.process(request)
            else:
                # 레거시 시스템으로 라우팅
                return self.legacy_system.process(request)
    
  2. 어댑터 성능 최적화

    • 목표: 어댑터 계층에서 발생하는 불필요한 반복 호출과 리소스 사용을 줄여 성능을 최적화
    • 전략:
      • 결과를 LRU 캐시에 저장해 반복 호출을 회피
      • 외부 시스템 호출 시 커넥션 풀을 활용해 연결 비용 절감
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    class OptimizedAdapter(PaymentProcessor):
        """성능 최적화된 어댑터"""
    
        def __init__(self, adaptee, cache_size=1000):
            self.adaptee = adaptee
            self.result_cache = LRUCache(cache_size)
            self.connection_pool = ConnectionPool(max_connections=10)
    
        def process_payment(self, amount, currency, payment_method, user_id):
            # 캐시 키 생성
            cache_key = f"{amount}_{currency}_{payment_method}_{user_id}"
    
            # 캐시 확인
            if cache_key in self.result_cache:
                return self.result_cache[cache_key]
    
            # 연결 풀에서 연결 가져오기
            with self.connection_pool.get_connection() as conn:
                # 실제 처리
                result = self._process_with_connection(conn, amount, currency, payment_method, user_id)
    
            # 결과 캐싱
            self.result_cache[cache_key] = result
            return result
    

활용 시 흔한 실수

  1. 인터페이스 설계 실수
    잘못된 예:

    1
    2
    3
    4
    5
    
    # 너무 세부적인 인터페이스
    class PaymentProcessor:
        def process_visa_payment(self, amount): pass
        def process_mastercard_payment(self, amount): pass
        def process_amex_payment(self, amount): pass
    

    올바른 예:

    1
    2
    3
    
    # 일반화된 인터페이스
    class PaymentProcessor:
        def process_payment(self, amount, payment_method): pass
    
  2. 과도한 어댑터 사용
    문제점: 단순한 매핑도 어댑터로 처리
    해결책: 설정 기반 매핑이나 간단한 변환 함수 사용

  3. 예외 처리 누락
    잘못된 예:

    1
    2
    3
    
    def process_payment(self, amount, currency, payment_method, user_id):
        result = self.adaptee.charge(amount * 100)  # 예외 처리 없음
        return PaymentResult(PaymentStatus.SUCCESS, result.id)
    

    올바른 예:

    1
    2
    3
    4
    5
    6
    7
    8
    
    def process_payment(self, amount, currency, payment_method, user_id):
        try:
            result = self.adaptee.charge(amount * 100)
            return PaymentResult(PaymentStatus.SUCCESS, result.id)
        except AdapteeException as e:
            return PaymentResult(PaymentStatus.FAILED, "", str(e))
        except Exception as e:
            return PaymentResult(PaymentStatus.FAILED, "", f"Unexpected error: {e}")
    

성능 최적화를 위한 고려사항

카테고리항목설명권장사항
설계 최적화위임 구조 최소화Adapter 내부의 단순 위임 계층이 과도하면 성능 오버헤드 발생단순 위임은 제거하거나 통합 고려, Adapter 내 관심사 분리
인터페이스 기반 설계다양한 시스템 간 유연한 연결 및 테스트 가능성 확보인터페이스 중심 설계 및 DI 적용
Lazy 초기화무거운 Adaptee 객체는 초기 호출 시점까지 생성 지연Provider, Lazy 주입 또는 Proxy 지연 초기화 활용
불필요 계층 제거단순한 통합에도 불필요한 Adapter 계층 도입 시 오히려 복잡성 증가사전 분석을 통해 직접 통합 또는 경량화 설계
리소스 관리객체 생성 비용Adapter 또는 Adaptee 객체 생성이 잦을 경우 리소스 낭비싱글톤, 객체 풀, 플라이웨이트 패턴 활용
메모리 관리어댑터가 Adaptee 를 장기 참조하거나 캐싱 시 누수 발생 가능WeakReference, 주기적 정리, 수명 주기 관리 적용
캐싱 전략변환 결과나 외부 호출 결과의 반복 호출 시 성능 저하불변 객체 기반 캐싱, Guava Cache, Spring Cache 등 적용
실행 최적화배치 처리대량 데이터를 1 건씩 처리하면 성능 저하 발생Stream API, 일괄 처리 전략 (Batch Adapter) 적용
비동기 처리네트워크 호출, 외부 API 연동 등에서 블로킹 발생 가능CompletableFuture, async/await, Reactive Adapter 도입
네트워크 호출 최소화Adaptee 가 외부 시스템일 경우 불필요한 호출로 인한 지연 시간 증가호출 결과 캐싱, 연결 풀 활용, 요청 합치기 등 적용
테스트 전략테스트 자동화다양한 조합에서의 Adapter 기능 테스트 필요Mock Adapter, 테스트 더블 패턴 사용
성능 모니터링Adapter 계층의 성능 병목 원인 추적 필요APM 도구 (New Relic, Datadog 등) 통한 트레이싱 및 메트릭 수집
운영 안정성스레드 안전성멀티스레드 환경에서 공유 객체 상태가 변경되면 동시성 문제 발생불변 객체 설계, 락 -Free 구조, 동기화 처리 (Lock, Thread-safe Collection)
장애 격리Adaptee 장애가 전체 시스템에 영향 미치는 경우Circuit Breaker, 타임아웃 설정, 대체 로직 구성
데이터 일관성멀티 호출 시 동일한 결과 일관성 보장 필요상태 분리, 동기화 구조 적용 또는 외부 상태는 별도 전달
확장성과 유연성수평 확장 고려Adapter 가 병목이 되지 않도록 무상태 구조 권장Stateless 설계, Auto Scaling 및 Load Balancing 적용
런타임 어댑터 관리다양한 Adapter 를 런타임에 유연하게 관리하는 구조 필요Adapter Registry 또는 Plugin 기반 동적 로딩 구조 활용
모듈화 설계기능별 Adapter 를 분리하여 유지보수 및 재사용 용이기능 중심으로 Adapter 분리, 각기 다른 도메인에 특화된 Adaptee 인터페이스 설계 적용

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

카테고리주제항목설명
설계 원칙Adapter호환성서로 다른 인터페이스를 가진 클래스들이 함께 동작 가능하게 함
기존 코드 보존기존 코드를 수정하지 않고 새로운 인터페이스에 적응
Object vs Class Adapter구현 방식객체 위임 기반 vs 클래스 상속 기반 (다중 상속 지원 여부에 따라 선택)
SRP/OCP단일 책임, 개방/폐쇄 원칙 적용어댑터를 통해 변경에 닫히고 확장에 열려 있는 구조 구현
실무 활용외부 API 연동외부 API 어댑터외부 시스템의 응답 형식을 내부 시스템 형식으로 변환하여 통합
레거시 통합레거시 어댑터레거시 시스템의 인터페이스를 변경하지 않고 신 시스템에 통합
데이터베이스Database Adapter다중 벤더/종류의 DB 를 하나의 인터페이스로 추상화하여 통합
메시징 시스템Protocol Adapter서로 다른 메시징 프로토콜 (MQTT, Kafka 등) 을 중계
아키텍처 연계MicroservicesAPI GatewayREST/gRPC 등 이질적 프로토콜 간 어댑팅
KubernetesService Mesh서비스 간 통신 시 TLS, gRPC, HTTP 등 프로토콜 자동 변환
데이터 통합Data Mapper다양한 데이터 소스 간 포맷/스키마 변환 어댑터
RESTful APIAPI Versioning버전 간의 호환성을 위한 요청/응답 변환 계층
성능 및 최적화Adapter 계층성능 오버헤드계층 증가로 인한 지연 발생 가능 → 주의 필요
캐싱/비동기 처리최적화 어댑터비동기 변환 처리 및 캐싱을 통한 성능 향상 (Reactive Adapter 등)
비동기 어댑터Reactive StreamsCompletableFuture, RxPy 등 기반 비동기 어댑터 구현
보안/관찰성인증 통합Identity AdapterOAuth, SAML, JWT 등 인증 체계 간 변환
모니터링 통합Metrics Adapter서로 다른 메트릭 수집 도구 (Prometheus, Datadog 등) 통합
테스트 전략테스트 더블Mock 어댑터테스트용 대체 어댑터로 테스트 더블/Stub/Fake 구현 지원
DI 기반 테스트인터페이스 기반 설계의존성 주입을 활용한 테스트 가능성 확보
확장성 관리Adapter Registry런타임 동적 어댑터 로딩플러그인 시스템 등에서 런타임에 어댑터 자동 로딩 및 관리
프로그래밍 패턴함수형 어댑터고차 함수 기반함수 커링 및 조합으로 인터페이스 어댑팅 구현
제네릭 어댑터타입 안전성TypeScript, Kotlin 등의 제네릭 기반으로 타입 안정성과 재사용성 확보
패턴 비교/연계래퍼 패턴 (Wrapper)대표 구현 형태Adapter 는 Wrapper 의 대표적 활용 사례
데코레이터/프록시 패턴구조적 유사성Adapter 는 목적이 인터페이스 호환, Decorator/Proxy 는 기능 확장/통제

추가 학습 필요 내용

카테고리주제항목/키워드설명
디자인 패턴어댑터 패턴 (Adapter Pattern)핵심 구조 / 역할Target, Adapter, Adaptee 간 인터페이스 변환 구조
관련 패턴 비교Bridge / Decorator / Proxy / Facade목적과 구현 방식의 차이 분석
고급 패턴 확장체인 어댑터 / 전략 어댑터 / 팩토리 어댑터전략, 생성, 연결 기능이 결합된 고급 어댑터 구성
아키텍처 설계아키텍처 패턴 적용헥사고날 아키텍처 / 클린 아키텍처포트와 어댑터 구조에서의 계층 설계와 의존성 역전 구현
ACL (Anti-Corruption Layer)외부 시스템 캡슐화 계층시스템 경계를 보호하며 어댑터의 확장된 역할 수행
마이크로서비스 통합 구조API Gateway / Service Mesh엔드포인트와 내부 시스템 간의 어댑터 역할 수행
레거시 및 외부 시스템 통합 전략외부 API 어댑터 / 서드파티 라이브러리직접 수정 불가한 시스템과 연동을 위한 어댑터 활용
프레임워크 활용어댑터 적용 예제Spring HandlerAdapter / ViewResolver프레임워크 내부에서의 어댑터 구현 사례 파악
서버리스 아키텍처 내 어댑터AWS Lambda / Azure Functions입력/출력 포맷 변환, 이벤트 처리기 인터페이스 조정 등
구현 전략구현 방식객체 어댑터 (합성) / 클래스 어댑터 (상속)위임과 상속의 구조적 차이와 상황별 적용 전략
객체 생명주기 최적화Lazy Loading / Adapter Pooling성능을 위한 어댑터 생성 비용 절감 전략
동적 선택 전략전략 어댑터실행 환경 또는 입력 조건에 따라 어댑터 인스턴스를 런타임에 선택
테스트 기법테스트 설계Mock Adapter / Stub / Integration Test의존성 격리 및 시스템 통합 검증을 위한 테스트 방식
테스트 전략Mock 기반 단위 테스트어댑터 계층의 기능 단위 테스트와 시나리오 테스트 구성
성능 최적화실행 효율LRU Cache / Lazy Initialization변환 결과의 캐싱 및 지연 초기화를 통한 리소스 최적화
오버헤드 최소화경량 어댑터 설계 / 불필요 호출 제거성능 병목 제거와 효율적인 변환 처리 방식 설계
설계 원칙 적용SOLID 원칙SRP / OCP / ISP / DIP객체 책임 분리, 확장성 확보, 인터페이스 분리, 추상화 의존성 적용 등
의존성 역전고수준 모듈 ↔ 저수준 모듈 간 추상화 연결클린 아키텍처의 핵심 설계 원칙 반영
통합 및 자동화자동 생성 도구 활용어댑터 코드 생성기 / 인터페이스 매핑 도구반복적인 어댑터 생성/관리 자동화로 생산성 향상
메시지/데이터 변환 패턴 적용Message Translator / ETL Adapter형식이 다른 메시지 또는 데이터 스트림을 변환하는 어댑터 응용 사례
학습 보조 개념내부 동작 이해위임 / 인터페이스 변환 / 변환 전략어댑터 내부 로직과 설계 원리 이해를 위한 기반 지식
Wrapper 개념기존 객체 감싸기 구조 패턴인터페이스 재정의 또는 확장 구조로서의 어댑터 이해
Thread Safety / Immutable 객체동시성 고려 설계멀티스레드 환경에서 어댑터 객체의 안정성 확보 전략

용어 정리

카테고리용어설명
패턴 구성 요소Target클라이언트가 기대하는 표준 인터페이스
Adaptee기존 시스템/서드파티 라이브러리 등, 호환되지 않는 인터페이스를 가진 클래스
AdapterTarget 인터페이스를 구현하고, 내부적으로 Adaptee 를 호출하여 인터페이스를 변환하는 중개 클래스
Client비즈니스 로직을 실행하며 Target 인터페이스를 통해 Adapter 를 사용하는 객체
Wrapper (래퍼)어댑터 패턴의 또 다른 이름으로, 기존 객체를 감싸 인터페이스를 변환
Anti-Corruption Layer외부 시스템과의 의존성을 줄이기 위해 경계 역할을 수행하는 어댑터 계층
구현 방식Object Adapter합성 (composition) 을 사용하여 Adaptee 인스턴스를 포함하는 방식
Class Adapter다중 상속을 통해 Target 과 Adaptee 를 모두 상속받아 구현하는 방식
Two-way Adapter양방향 변환이 가능한 어댑터로, Adaptee 와 Target 역할을 모두 수행
설계 원칙Single Responsibility Principle (SRP)클래스는 하나의 책임만 가져야 한다는 객체지향 설계 원칙
Open-Closed Principle (OCP)확장에는 열려 있고, 변경에는 닫혀 있어야 한다는 원칙
Interface Segregation Principle (ISP)클라이언트가 사용하지 않는 인터페이스에 의존하지 않도록 분리해야 한다는 원칙
Dependency Inversion Principle (DIP)고수준 모듈이 저수준 모듈에 의존하지 않고 추상화에 의존해야 한다는 원칙
기술 개념Interface Translation서로 다른 인터페이스의 메서드 호출을 변환하는 과정
Delegation (위임)어댑터가 포함하고 있는 객체 (Adaptee) 에게 기능 수행을 위임하는 구조
Object Composition (객체 합성)상속 대신 객체를 포함하여 기능을 구성하는 방식
소프트웨어 배경Legacy System오래된 기술로 구축되어 있고, 직접 변경이 어려운 기존 시스템
Third-party Library외부에서 제공된 라이브러리로, 직접 수정이 어렵고 어댑터를 통해 통합되는 경우가 많음
아키텍처 요소API Gateway외부 클라이언트의 요청을 내부 서비스로 라우팅하는 진입점 역할 수행 (Adapter 역할과 유사한 목적)
Service Mesh서비스 간 통신을 추상화하여 관리하는 인프라 계층 (Adapter 역할 포함 가능)
통합 및 변환Message Translator서로 다른 메시지 형식을 변환해주는 구성 요소 (Adapter 의 일종으로 사용됨)
ETL (Extract, Transform, Load)데이터를 추출, 변환, 적재하는 통합 프로세스로, Adaptee ↔ Target 데이터 흐름의 구현 예시
보조 개념Mock Object테스트에서 실제 객체 대신 사용하는 가짜 객체 (어댑터 테스트 시 사용됨)
Connection Pooling재사용 가능한 연결을 유지하여 성능을 최적화하는 기법
LRU Cache가장 오래된 항목부터 제거하는 캐싱 알고리즘
Circuit Breaker외부 시스템 장애가 발생했을 때 시스템 보호를 위한 패턴
Bulkhead Pattern장애가 전체 시스템에 영향을 주지 않도록 격리하는 구조
Serialization객체를 바이트 스트림으로 변환하는 과정
Marshalling객체를 JSON, XML 등 포맷으로 변환하는 과정
IdP (Identity Provider)사용자 인증을 제공하는 시스템
Distributed Tracing요청 흐름을 추적하여 분산 시스템에서 문제를 진단하는 기술
SLA (Service Level Agreement)서비스 제공자와 사용자 간의 품질 보장 합의

참고 및 출처

공식 및 이론 기반

튜토리얼 및 실용 예제

아키텍처·프레임워크 관련

심화·최신 분석