이종관
글 목록으로

구조 패턴(Structural Patterns): 객체 합성과 인터페이스 설계

Adapter, Facade, Decorator, Proxy, Composite, Bridge와 Sidecar/BFF 아키텍처 패턴

2025년 1월 11일·15 min read·
architecture
oop
design-pattern
structural-pattern
microservice

개요

구조 패턴은 클래스와 객체를 더 큰 구조로 합성하는 방법을 다룬다. 인터페이스의 호환성을 맞추고, 복잡한 시스템에 단순한 접근 방법을 제공하며, 기존 객체에 새로운 기능을 동적으로 추가하는 것이 핵심이다. 마이크로서비스 시대에는 서비스 간 통신을 구조화하는 아키텍처 패턴으로 확장되었다.

Adapter 패턴 (적응자)

정의

호환되지 않는 인터페이스를 변환하여 함께 동작할 수 있도록 하는 패턴이다. "래퍼(Wrapper)"라고도 한다.

실전 사례: 레거시 결제 시스템 통합

java
// 기존 시스템의 레거시 결제 인터페이스
public class LegacyPaymentSystem {
    public boolean processPayment(String cardNumber, double amount, String currency) {
        // 레거시 로직...
        return true;
    }
}
 
// 새로운 표준 인터페이스
public interface PaymentProcessor {
    PaymentResult charge(PaymentRequest request);
}
 
// Adapter: 레거시 시스템을 새 인터페이스에 맞춤
public class LegacyPaymentAdapter implements PaymentProcessor {
    private final LegacyPaymentSystem legacySystem;
 
    public LegacyPaymentAdapter(LegacyPaymentSystem legacySystem) {
        this.legacySystem = legacySystem;
    }
 
    @Override
    public PaymentResult charge(PaymentRequest request) {
        boolean success = legacySystem.processPayment(
            request.getCardNumber(),
            request.getAmount().doubleValue(),
            request.getCurrency().getCode()
        );
        return new PaymentResult(success, success ? "OK" : "FAILED");
    }
}

Adapter의 두 가지 유형

유형방식특징
객체 어댑터 (Object Adapter)합성(Composition)유연하며 권장됨
클래스 어댑터 (Class Adapter)상속(Inheritance)다중 상속 필요, 제한적

활용 사례

  • 외부 API 통합 (서드파티 라이브러리 래핑)
  • 레거시 시스템 마이그레이션 (점진적 교체)
  • 데이터 형식 변환 (XML ↔ JSON)
  • ORM 어댑터 (다양한 DB 드라이버 호환)

Adapter와 헥사고날 아키텍처 (Hexagonal Architecture)

헥사고날 아키텍처에서 Adapter는 핵심 구성 요소이다:

plaintext
┌─────────────────────────────────────────┐
│              Application Core           │
│  ┌─────────────────────────────────┐    │
│  │      Domain Logic (Ports)       │    │
│  └─────────┬───────────┬──────────┘    │
│            │           │                │
│  ┌─────────▼──┐  ┌─────▼──────────┐    │
│  │  Driving    │  │  Driven        │    │
│  │  Adapter    │  │  Adapter       │    │
│  │  (REST API) │  │  (PostgreSQL)  │    │
│  └────────────┘  └────────────────┘    │
└─────────────────────────────────────────┘
  • Driving Adapter: 외부 요청을 내부 포트에 연결 (REST Controller, gRPC Handler)
  • Driven Adapter: 내부 포트의 요청을 외부 시스템에 연결 (DB Repository, Message Publisher)

Facade 패턴 (퍼사드)

정의

복잡한 하위 시스템에 대한 단순화된 통합 인터페이스를 제공하는 패턴이다. 클라이언트가 하위 시스템의 복잡성을 알 필요 없이 핵심 기능을 사용할 수 있게 한다.

구현 예시: 주문 처리 Facade

java
// 복잡한 하위 시스템들
public class InventoryService { /* 재고 관리 */ }
public class PaymentService { /* 결제 처리 */ }
public class ShippingService { /* 배송 관리 */ }
public class NotificationService { /* 알림 발송 */ }
public class FraudDetectionService { /* 사기 탐지 */ }
 
// Facade: 복잡한 주문 프로세스를 단순한 인터페이스로 제공
public class OrderFacade {
    private final InventoryService inventory;
    private final PaymentService payment;
    private final ShippingService shipping;
    private final NotificationService notification;
    private final FraudDetectionService fraud;
 
    public OrderFacade(InventoryService inventory, PaymentService payment,
                       ShippingService shipping, NotificationService notification,
                       FraudDetectionService fraud) {
        this.inventory = inventory;
        this.payment = payment;
        this.shipping = shipping;
        this.notification = notification;
        this.fraud = fraud;
    }
 
    // 클라이언트는 이 메서드만 호출하면 됨
    public OrderResult placeOrder(OrderRequest request) {
        // 1. 사기 탐지
        fraud.validate(request);
        // 2. 재고 확인 및 예약
        inventory.reserve(request.getItems());
        // 3. 결제 처리
        PaymentResult paymentResult = payment.charge(request.getPayment());
        // 4. 배송 요청
        ShippingLabel label = shipping.createLabel(request.getAddress());
        // 5. 알림 발송
        notification.sendOrderConfirmation(request.getCustomer());
 
        return new OrderResult(paymentResult, label);
    }
}

API Gateway: 마이크로서비스의 Facade

마이크로서비스 아키텍처에서 API Gateway는 Facade 패턴의 아키텍처적 구현이다:

plaintext
클라이언트 ──→ [API Gateway (Facade)] ──→ User Service
                                      ──→ Order Service
                                      ──→ Payment Service
                                      ──→ Inventory Service

API Gateway의 책임:

책임설명
라우팅 (Routing)요청을 적절한 서비스로 전달
집계 (Aggregation)여러 서비스의 응답을 하나로 합성
인증/인가 (Auth)중앙 집중식 인증 처리
속도 제한 (Rate Limiting)서비스별 요청 제한
프로토콜 변환REST ↔ gRPC, WebSocket 등 변환

대표적 구현체: Kong, AWS API Gateway, Envoy, NGINX

Decorator 패턴 (장식자)

정의

기존 객체에 새로운 책임을 동적으로 추가하는 패턴이다. 상속(Inheritance)의 대안으로, 런타임에 기능을 조합할 수 있어 더 유연하다.

구현 예시: 로깅 + 캐싱 + 재시도

java
// 기본 인터페이스
public interface DataService {
    Data fetch(String key);
}
 
// 기본 구현
public class RemoteDataService implements DataService {
    public Data fetch(String key) {
        return callRemoteApi(key);
    }
}
 
// Decorator: 로깅 추가
public class LoggingDecorator implements DataService {
    private final DataService delegate;
    private final Logger logger;
 
    public LoggingDecorator(DataService delegate, Logger logger) {
        this.delegate = delegate;
        this.logger = logger;
    }
 
    public Data fetch(String key) {
        logger.info("Fetching data for key: {}", key);
        long start = System.nanoTime();
        Data result = delegate.fetch(key);
        long elapsed = System.nanoTime() - start;
        logger.info("Fetched in {}ms", elapsed / 1_000_000);
        return result;
    }
}
 
// Decorator: 캐싱 추가
public class CachingDecorator implements DataService {
    private final DataService delegate;
    private final Cache cache;
 
    public CachingDecorator(DataService delegate, Cache cache) {
        this.delegate = delegate;
        this.cache = cache;
    }
 
    public Data fetch(String key) {
        Data cached = cache.get(key);
        if (cached != null) return cached;
        Data result = delegate.fetch(key);
        cache.put(key, result);
        return result;
    }
}
 
// Decorator: 재시도 추가
public class RetryDecorator implements DataService {
    private final DataService delegate;
    private final int maxRetries;
 
    public RetryDecorator(DataService delegate, int maxRetries) {
        this.delegate = delegate;
        this.maxRetries = maxRetries;
    }
 
    public Data fetch(String key) {
        for (int i = 0; i < maxRetries; i++) {
            try {
                return delegate.fetch(key);
            } catch (Exception e) {
                if (i == maxRetries - 1) throw e;
            }
        }
        throw new RuntimeException("Unreachable");
    }
}
 
// 조합: 로깅 → 캐싱 → 재시도 → 실제 호출
DataService service = new LoggingDecorator(
    new CachingDecorator(
        new RetryDecorator(
            new RemoteDataService(), 3
        ), redisCache
    ), logger
);

Decorator vs 상속

비교 항목Decorator상속
기능 추가 시점런타임 (동적)컴파일타임 (정적)
조합 가능성자유롭게 조합클래스 폭발 문제
개방-폐쇄 원칙준수위반 가능성 높음
복잡도중첩이 깊어질 수 있음상속 트리가 깊어질 수 있음

실전 활용

  • Java I/O 스트림: BufferedReader(FileReader(file))
  • HTTP 미들웨어: Express.js, Koa의 미들웨어 체인
  • AOP(Aspect-Oriented Programming): Spring AOP의 프록시 기반 데코레이팅

Proxy 패턴 (대리자)

정의

다른 객체에 대한 **접근을 제어하는 대리 객체(Surrogate)**를 제공하는 패턴이다.

Proxy의 유형

유형목적예시
가상 프록시 (Virtual Proxy)지연 로딩 (Lazy Loading)대용량 이미지 로딩 지연
보호 프록시 (Protection Proxy)접근 제어 (Access Control)권한 기반 메서드 호출 제한
원격 프록시 (Remote Proxy)원격 객체의 로컬 대리인RPC/gRPC 스텁
캐싱 프록시 (Caching Proxy)결과 캐싱반복 호출 결과 저장
로깅 프록시 (Logging Proxy)요청 기록메서드 호출 로그

구현 예시: 보호 프록시

java
public interface UserRepository {
    User findById(String id);
    void delete(String id);
}
 
// 접근 제어 프록시
public class SecuredUserRepository implements UserRepository {
    private final UserRepository delegate;
    private final SecurityContext securityContext;
 
    public SecuredUserRepository(UserRepository delegate,
                                  SecurityContext securityContext) {
        this.delegate = delegate;
        this.securityContext = securityContext;
    }
 
    @Override
    public User findById(String id) {
        // 읽기는 인증만 확인
        securityContext.requireAuthentication();
        return delegate.findById(id);
    }
 
    @Override
    public void delete(String id) {
        // 삭제는 ADMIN 권한 필요
        securityContext.requireRole("ADMIN");
        delegate.delete(id);
    }
}

Java Dynamic Proxy / CGLIB

Spring Framework는 내부적으로 Dynamic ProxyCGLIB를 사용하여 AOP를 구현한다:

java
// Spring AOP: @Transactional은 Proxy로 구현됨
@Service
public class OrderService {
    @Transactional  // 이 메서드 호출 시 Proxy가 트랜잭션 시작/종료
    public void createOrder(OrderRequest request) {
        // 비즈니스 로직만 집중
    }
}

Composite 패턴 (복합체)

정의

객체를 트리 구조로 합성하여 부분-전체 계층 구조를 표현하는 패턴이다. 개별 객체와 복합 객체를 동일하게 취급할 수 있다.

구현 예시: 파일 시스템

java
public interface FileSystemNode {
    String getName();
    long getSize();
    void print(String indent);
}
 
public class File implements FileSystemNode {
    private final String name;
    private final long size;
 
    public String getName() { return name; }
    public long getSize() { return size; }
    public void print(String indent) {
        System.out.println(indent + name + " (" + size + "B)");
    }
}
 
public class Directory implements FileSystemNode {
    private final String name;
    private final List<FileSystemNode> children = new ArrayList<>();
 
    public void add(FileSystemNode node) { children.add(node); }
    public String getName() { return name; }
 
    public long getSize() {
        return children.stream()
            .mapToLong(FileSystemNode::getSize)
            .sum(); // 재귀적으로 전체 크기 계산
    }
 
    public void print(String indent) {
        System.out.println(indent + name + "/");
        children.forEach(child -> child.print(indent + "  "));
    }
}

활용 사례

  • UI 컴포넌트 트리 (React Virtual DOM, Swing)
  • 조직도 (부서 → 팀 → 개인)
  • 메뉴 시스템 (메뉴 → 서브메뉴 → 메뉴 아이템)
  • 쿼리 빌더 (AND/OR 조건의 트리 구조)

Bridge 패턴 (다리)

정의

추상화(Abstraction)와 구현(Implementation)을 분리하여 각각 독립적으로 변경할 수 있게 하는 패턴이다.

구현 예시: 알림 시스템

java
// 구현 (Implementation) 계층: 발송 채널
public interface NotificationChannel {
    void send(String recipient, String content);
}
public class EmailChannel implements NotificationChannel { /* ... */ }
public class SlackChannel implements NotificationChannel { /* ... */ }
public class SmsChannel implements NotificationChannel { /* ... */ }
 
// 추상화 (Abstraction) 계층: 알림 유형
public abstract class Notification {
    protected final NotificationChannel channel;
 
    public Notification(NotificationChannel channel) {
        this.channel = channel;
    }
 
    public abstract void notify(String recipient, Map<String, Object> data);
}
 
public class UrgentNotification extends Notification {
    public UrgentNotification(NotificationChannel channel) {
        super(channel);
    }
 
    public void notify(String recipient, Map<String, Object> data) {
        String content = "[URGENT] " + formatMessage(data);
        channel.send(recipient, content);
    }
}
 
public class ScheduledNotification extends Notification {
    // 예약 발송 로직...
}

Bridge의 핵심 가치

  • 알림 유형 (긴급, 예약, 일반)과 발송 채널 (이메일, Slack, SMS)이 독립적으로 확장
  • M x N 조합 문제를 M + N으로 축소
  • 런타임에 구현을 교체할 수 있음

마이크로서비스 시대의 구조 패턴

Sidecar 패턴

마이크로서비스 옆에 **보조 프로세스(Sidecar)**를 배치하여 횡단 관심사(Cross-cutting Concerns)를 처리하는 패턴이다.

plaintext
┌─────────────────────────────┐
│          Pod (K8s)          │
│  ┌──────────┐ ┌──────────┐ │
│  │ Main     │ │ Sidecar  │ │
│  │ Service  │↔│ Proxy    │ │
│  │ (비즈니스 │ │ (Envoy)  │ │
│  │  로직)    │ │          │ │
│  └──────────┘ └──────────┘ │
└─────────────────────────────┘

Sidecar가 처리하는 횡단 관심사:

  • 서비스 디스커버리 (Service Discovery)
  • 로드 밸런싱 (Load Balancing)
  • 암호화/TLS 종료 (mTLS)
  • 메트릭 수집 (Observability)
  • 서킷 브레이커 (Circuit Breaking)
  • 재시도/타임아웃 (Retry/Timeout)

서비스 메시 (Service Mesh): Sidecar 패턴을 전체 클러스터에 적용한 인프라 계층

서비스 메시사이드카 프록시특징
IstioEnvoy가장 기능이 풍부, 복잡도 높음
Linkerdlinkerd2-proxy (Rust)경량, 간결한 설정
Consul ConnectEnvoyHashiCorp 생태계와 통합
CiliumeBPF 기반Sidecar 없이 커널 수준에서 처리 (2026 트렌드)

2026년 트렌드: eBPF 기반의 Sidecar-less 서비스 메시(Cilium, Ambient Mesh)가 기존 Sidecar 방식의 성능 오버헤드를 해결하며 부상하고 있다.

BFF 패턴 (Backend for Frontend)

클라이언트 유형별로 전용 백엔드 서비스를 제공하는 패턴이다. ISP(인터페이스 분리 원칙)의 아키텍처적 적용이다.

plaintext
Mobile App ──→ [Mobile BFF] ──→ User Service
                              ──→ Order Service (경량 응답)
 
Web App ────→ [Web BFF]    ──→ User Service
                              ──→ Order Service (상세 응답)
                              ──→ Analytics Service
 
Admin ──────→ [Admin BFF]  ──→ User Service (관리 기능)
                              ──→ Audit Service

BFF의 장점:

  • 클라이언트별 최적화된 API 응답 (모바일: 경량, 웹: 상세)
  • 프론트엔드 팀이 독립적으로 BFF를 수정 가능
  • SOLID 원칙의 ISP를 아키텍처 수준에서 실현
  • GraphQL Federation과 결합하면 더욱 유연

구조 패턴 비교표

패턴목적핵심 메커니즘대표 활용
Adapter인터페이스 호환래핑 (Wrapping)레거시 통합, 외부 API
Facade복잡성 은닉통합 인터페이스API Gateway, SDK
Decorator동적 기능 추가중첩 래핑미들웨어, I/O 스트림
Proxy접근 제어대리 객체AOP, 지연 로딩
Composite트리 구조재귀적 합성UI, 파일 시스템
Bridge추상-구현 분리이중 계층알림, 드라이버
Sidecar횡단 관심사 분리보조 프로세스서비스 메시
BFF클라이언트별 최적화전용 백엔드멀티 플랫폼 API