Hollywood Principle
Hollywood Principle 은 객체지향 설계 및 프레임워크 설계에서 널리 쓰이는 원칙으로, “Don’t call us, we’ll call you” 라는 문구로 대표된다. 이 원칙은 저수준 (구현) 모듈이 고수준 (프레임워크, 추상화) 모듈을 직접 호출하는 것이 아니라, 고수준 모듈이 저수준 모듈을 필요할 때 호출하도록 구조를 설계한다. 이를 통해 의존성 부패 (Dependency Rot) 를 방지하고, 시스템의 유연성, 확장성, 테스트 용이성을 높인다. 대표적으로 Inversion of Control, Dependency Injection, Observer, Template Method, Strategy 패턴 등에서 적용된다.
핵심 개념
Hollywood Principle 은 **“Don’t call us, we’ll call you”**라는 문구로 요약되며, 저수준 컴포넌트는 고수준 컴포넌트의 호출을 기다리는 방식으로 제어 흐름을 관리한다. 느슨한 결합, 의존성 부패 방지, 확장성 및 테스트 용이성 확보를 목적으로 하며 프레임워크 설계, 이벤트 기반 시스템, 디자인 패턴 (Observer, Template Method, Strategy 등) 에 적용된다.
- 제어의 역전 (Inversion of Control, IoC)
- 프로그램의 제어 흐름이 애플리케이션 코드가 아닌 외부 프레임워크에서 결정되는 원칙
- 전통적인 “call” 방식에서 “callback” 방식으로의 패러다임 전환
- 콜백 메커니즘 (Callback Mechanism)
- 특정 이벤트나 조건 발생 시 자동으로 호출되는 함수나 메서드
- 비동기 프로그래밍과 이벤트 처리의 핵심 메커니즘
- 의존성 주입 (Dependency Injection, DI)
- 객체가 필요한 의존성을 생성하지 않고 외부에서 제공받는 방식
- 객체 생성과 사용의 책임을 분리하여 결합도를 낮춤
- 이벤트 기반 아키텍처 (Event-driven Architecture)
- 이벤트의 발생과 처리를 중심으로 시스템을 설계하는 방식
- Publisher-Subscriber 패턴의 기반이 되는 아키텍처 스타일
- 느슨한 결합 (Loose Coupling)
- 컴포넌트 간 의존성을 최소화하여 변경에 대한 영향을 줄이는 설계 방식
- 높은 응집도 (High Cohesion) 와 함께 소프트웨어 품질의 핵심 지표
- 프레임워크 vs 라이브러리 구분
- 프레임워크: 애플리케이션 코드를 호출하는 주체
- 라이브러리: 애플리케이션 코드에 의해 호출되는 대상
- Publisher-Subscriber 패턴
- 이벤트 발행자와 구독자 간의 비동기 통신을 지원하는 패턴
- 일대다 통신과 느슨한 결합을 동시에 실현
배경 및 필요성
Hollywood Principle 은 1980 년대 후반 객체지향 프로그래밍의 발전과 함께 등장했다. Martin Fowler 가 1988 년으로 그 기원을 추적하였으며, 할리우드 오디션에서 흔히 듣는 “Don’t call us, we’ll call you” 문구에서 이름이 유래되었다.
필요성:
- 코드의 재사용성 향상
- 컴포넌트 간 결합도 감소
- 테스트 가능성 증대
- 확장성과 유지보수성 개선
- 관심사의 분리 실현
주요 기능 및 역할
- 제어 흐름 관리: 프레임워크가 애플리케이션의 실행 흐름을 제어
- 의존성 관리: 객체 간 의존성을 외부에서 주입하여 관리
- 이벤트 처리: 비동기적 이벤트 발생과 처리를 조율
- 생명주기 관리: 객체의 생성, 초기화, 소멸을 프레임워크가 관리
- 확장점 제공: 플러그인이나 확장 모듈을 위한 훅 (Hook) 제공
특징
- 비침입적 (Non-intrusive): 비즈니스 로직에 프레임워크 코드가 침투하지 않음
- 선언적 (Declarative): 설정이나 어노테이션을 통한 선언적 프로그래밍
- 확장가능 (Extensible): 새로운 구현체를 쉽게 추가할 수 있는 구조
- 테스트 친화적: Mock 객체를 쉽게 주입하여 단위 테스트 용이
핵심 원칙
- “Don’t call us, we’ll call you”: 능동적 호출에서 수동적 대기로의 전환
- 제어권 위임: 애플리케이션 코드가 제어권을 프레임워크에 위임
- 인터페이스 기반 설계: 구체 클래스 대신 인터페이스에 의존
- 관심사 분리: 비즈니스 로직과 인프라스트럭처 코드의 분리
작동 원리
- 저수준 컴포넌트 (플러그인, 리스너 등) 는 고수준 컴포넌트에 등록만 한다.
- 고수준 컴포넌트 (프레임워크, 이벤트 디스패처 등) 가 필요할 때 저수준 컴포넌트를 호출한다.
sequenceDiagram participant Client as Client Code participant Framework as Framework participant Callback as Callback Handler Note over Client,Callback: 1. 등록 단계 (Registration Phase) Client->>Framework: register(callback) Framework->>Framework: store callback in registry Note over Framework: 2. 대기 단계 (Waiting Phase) Note over Framework: Framework waits for events/conditions Note over Framework: 3. 트리거 단계 (Triggering Phase) Framework->>Framework: Internal event occurs Note over Framework,Callback: 4. 호출 단계 (Invocation Phase) Framework->>Callback: "Don't call us, we'll call you" Note over Callback: 5. 실행 단계 (Execution Phase) Callback->>Callback: Execute business logic Note over Framework: 6. 반환 단계 (Return Phase) Callback->>Framework: Return control/result Framework->>Client: Optional: notify completion
Hollywood Principle 의 작동 원리는 다음과 같은 단계로 구성된다:
- 등록 단계 (Registration): 클라이언트 코드가 프레임워크에 콜백이나 핸들러를 등록
- 대기 단계 (Waiting): 클라이언트는 능동적으로 호출하지 않고 대기
- 트리거 단계 (Triggering): 프레임워크 내부에서 특정 이벤트나 조건 발생
- 호출 단계 (Invocation): 프레임워크가 등록된 콜백을 호출
- 실행 단계 (Execution): 클라이언트의 비즈니스 로직 실행
- 반환 단계 (Return): 제어권이 프레임워크로 돌아감
구조 및 아키텍처
Hollywood Principle 은 고수준 모듈이 제어 흐름을 담당하고 저수준 모듈은 콜백, 이벤트 등록, 추상 클래스 상속 등을 통해 호출될 수 있는 상태를 유지하는 구조이다.
graph TB subgraph "Hollywood Principle 전체 아키텍처" Framework[Framework/Container] subgraph "필수 구성요소 (Essential Components)" Registry[Callback Registry] EventManager[Event Manager] LifecycleManager[Lifecycle Manager] end subgraph "선택 구성요소 (Optional Components)" ConfigManager[Configuration Manager] SecurityManager[Security Manager] MonitoringManager[Monitoring Manager] end subgraph "클라이언트 구성요소 (Client Components)" ClientCode[Client Code] CallbackHandlers[Callback Handlers] EventListeners[Event Listeners] end Framework --> Registry Framework --> EventManager Framework --> LifecycleManager Framework -.-> ConfigManager Framework -.-> SecurityManager Framework -.-> MonitoringManager ClientCode --> CallbackHandlers ClientCode --> EventListeners Registry --> CallbackHandlers EventManager --> EventListeners LifecycleManager --> ClientCode style Framework fill:#e8f5e8 style Registry fill:#e3f2fd style EventManager fill:#e3f2fd style LifecycleManager fill:#e3f2fd style ConfigManager fill:#fff3e0 style SecurityManager fill:#fff3e0 style MonitoringManager fill:#fff3e0 style ClientCode fill:#fce4ec style CallbackHandlers fill:#f3e5f5 style EventListeners fill:#f3e5f5 end
구성요소
구분 | 구성요소 | 기능 설명 | 역할 및 특징 |
---|---|---|---|
필수 | Framework / Container(프레임워크 / 컨테이너) | 시스템 전체 제어 흐름 담당콜백 실행, 생명주기 전반 제어 | 중앙 제어권 보유, 실행 흐름 결정 |
Callback Registry(콜백 레지스트리) | 콜백 함수 등록/해제/관리 | 동적 콜백 관리로 유연성 제공 | |
Event Manager(이벤트 매니저) | 이벤트 발생/전파/처리이벤트 큐 및 리스너 관리 | 비동기 이벤트 처리 지원 | |
Lifecycle Manager(생명주기 매니저) | 객체 생성~소멸까지 관리의존성 주입 시점 통제 | 리소스/의존성 관리, 전체 생명주기 제어 | |
선택 | Configuration Manager(설정 매니저) | 설정 파일 로딩 및 설정 값 관리 | 런타임 설정 변경 지원 |
Security Manager(보안 매니저) | 인증/인가, 접근 제어 수행 | **AOP(관심사 분리)**에 적합 | |
Monitoring Manager(모니터링 매니저) | 성능 지표 수집, 로깅, 시스템 상태 추적 | 실시간 모니터링 및 경보 시스템 연동 |
구현 기법
구분 | 패턴 / 기법 | 개념 요약 | 제어 흐름 | 목적 | 대표 기술 / 예시 |
---|---|---|---|---|---|
의존성 주입 | Dependency Injection (DI) | 의존 객체 생성을 외부에서 주입 | 객체 생성 제어권 → 컨테이너 | 결합도 감소, 테스트 용이성 향상 | Spring, NestJS, Angular, Guice |
관찰자 패턴 | Observer Pattern | 상태 변화 발생 시 등록된 리스너에 자동 통보 | 알림 제어권 → Subject | 상태 변화 감지, 다대일 연동 | EventEmitter, addEventListener |
템플릿 메서드 | Template Method Pattern | 알고리즘의 뼈대를 상위 클래스가 정의하고, 하위 클래스가 구현 | 알고리즘 순서 제어권 → 추상 클래스 | 알고리즘 재사용성, 확장성 | Spring JdbcTemplate, React Lifecycle |
이벤트 기반 | Event-driven Programming | 이벤트가 발생하면 리스너 또는 핸들러가 비동기적으로 반응 | 흐름 제어권 → 이벤트 루프/큐 | 느슨한 결합, 비동기 흐름 처리 | Node.js, Kafka, AWS EventBridge |
전략 패턴 | Strategy Pattern | 실행 알고리즘을 객체로 분리하고 유연하게 교체 | 실행 제어권 → 외부 전략 객체 | 런타임 유연성, 조건별 실행 방식 분리 | Array.prototype.sort(callback) |
서비스 등록소 | Service Locator Pattern | 객체를 전역 등록소에서 가져다 씀 (DI 의 대안적 방식) | 의존성 해결 제어권 → Locator | 의존성 중앙 관리 | Unity Container, InversifyJS |
이벤트 버스 | Event Bus / Message Bus | 메시지 중심으로 컴포넌트 간 통신 | 메시지 흐름 제어권 → 중앙 버스 | 컴포넌트 간 결합도 제거 | Vue EventBus, RabbitMQ, EventBridge |
콜백 등록 | Callback Registry | 콜백 함수를 동적으로 등록 및 해제 가능 | 호출 시점 제어권 → 외부 등록자 | 유연한 이벤트 대응 구조 | jQuery .on() , Node.js callbacks |
생명주기 관리 | Lifecycle Management | 객체 생성~소멸 주기를 일관되게 관리 | 자원 생명 제어권 → 컨테이너 | 자원 누수 방지, 예측 가능한 상태 전환 | React, Angular, Spring Context |
Dependency Injection (의존성 주입)
- 제어 역전: 객체 생성과 의존성 주입을 컨테이너가 담당
- 핵심: " 객체를 직접 생성하지 말고, 주입받아 사용하세요 "
graph TD subgraph "Dependency Injection Pattern" DI_Container[DI Container] DI_Service[Service Interface] DI_ServiceImpl[Service Implementation] DI_Client[Client Class] DI_Container -->|creates & injects| DI_ServiceImpl DI_Container -->|creates & injects| DI_Client DI_Client -.->|depends on| DI_Service DI_ServiceImpl -.->|implements| DI_Service style DI_Container fill:#e8f5e8 style DI_Service fill:#e3f2fd style DI_ServiceImpl fill:#fff3e0 style DI_Client fill:#fce4ec end
정의: 객체가 필요한 의존성을 생성하지 않고 외부에서 주입받는 기법
구성:
- Injector (주입자): 의존성을 제공하는 주체
- Service (서비스): 주입될 의존성 객체
- Client (클라이언트): 의존성을 필요로 하는 객체
목적: 객체 생성과 사용의 책임 분리, 결합도 감소
실제 예시:
Observer Pattern (관찰자 패턴)
- 제어 역전: 상태 변화 알림을 Subject 가 주도
- 핵심: " 변화를 감지하면 우리가 알려드리겠습니다 "
graph TD subgraph "Observer Pattern" Subject[Subject] Observer1[Observer 1] Observer2[Observer 2] ObserverN[Observer N] Subject -->|"notifyObservers()"| Observer1 Subject -->|"notifyObservers()"| Observer2 Subject -->|"notifyObservers()"| ObserverN Observer1 -.->|subscribe| Subject Observer2 -.->|subscribe| Subject ObserverN -.->|subscribe| Subject style Subject fill:#e8f5e8 style Observer1 fill:#e3f2fd style Observer2 fill:#e3f2fd style ObserverN fill:#e3f2fd end
정의: 객체의 상태 변화를 여러 관찰자에게 자동으로 알리는 패턴
구성:
- Subject (주제): 상태 변화의 주체
- Observer (관찰자): 변화를 감지하고 처리하는 객체
목적: 일대다 의존성 정의, 느슨한 결합 실현
실제 예시:
Template Method Pattern (템플릿 메서드 패턴)
- 제어 역전: 알고리즘 실행 순서를 상위 클래스가 제어
- 핵심: " 전체 흐름은 우리가 관리하고, 세부사항만 구현하세요 "
graph TD subgraph "Template Method Pattern" AbstractClass[Abstract Template Class] ConcreteClass1[Concrete Class 1] ConcreteClass2[Concrete Class 2] AbstractClass -->|calls| Hook1["primitiveOperation1()"] AbstractClass -->|calls| Hook2["primitiveOperation2()"] ConcreteClass1 -.->|implements| Hook1 ConcreteClass2 -.->|implements| Hook2 ConcreteClass1 -->|extends| AbstractClass ConcreteClass2 -->|extends| AbstractClass style AbstractClass fill:#e8f5e8 style ConcreteClass1 fill:#e3f2fd style ConcreteClass2 fill:#e3f2fd style Hook1 fill:#fff3e0 style Hook2 fill:#fff3e0 end
정의: 알고리즘의 골격을 정의하고 하위 클래스에서 세부 단계를 구현하는 패턴
구성:
- Abstract Class (추상 클래스): 템플릿 메서드 정의
- Concrete Class (구체 클래스): 세부 단계 구현
목적: 알고리즘 구조 고정, 세부 구현의 변경 허용
실제 예시:
Event-driven Programming (이벤트 기반 프로그래밍)
- 제어 역전: 이벤트 발생과 처리를 이벤트 버스가 조율
- 핵심: " 이벤트가 발생하면 우리가 처리하겠습니다 "
graph TD subgraph "Event-Driven Pattern" EventProducer[Event Producer] EventBus[Event Bus] EventConsumer1[Event Consumer 1] EventConsumer2[Event Consumer 2] EventQueue[Event Queue] EventProducer -->|publish| EventBus EventBus -->|enqueue| EventQueue EventQueue -->|dispatch| EventConsumer1 EventQueue -->|dispatch| EventConsumer2 EventConsumer1 -.->|subscribe| EventBus EventConsumer2 -.->|subscribe| EventBus style EventProducer fill:#e8f5e8 style EventBus fill:#e3f2fd style EventQueue fill:#fff3e0 style EventConsumer1 fill:#fce4ec style EventConsumer2 fill:#fce4ec end
정의: 이벤트 발생에 따라 프로그램 흐름이 결정되는 프로그래밍 방식
구성:
- Event Producer (이벤트 생산자): 이벤트를 발생시키는 주체
- Event Bus (이벤트 버스): 이벤트 전달 매개체
- Event Consumer (이벤트 소비자): 이벤트를 처리하는 주체
목적: 비동기적 상호작용 처리, 시스템 간 결합도 감소
실제 예시:
장점과 단점
구분 | 항목 | 설명 |
---|---|---|
✅ 장점 | 느슨한 결합 (Low Coupling) | 컴포넌트 간 직접적인 의존성이 줄어들어 변경에 유연하게 대응 가능 |
높은 응집도 (High Cohesion) | 각 모듈이 자신이 맡은 역할에 집중할 수 있도록 구조화됨 | |
확장성 (Extensibility) | 새로운 구현체 또는 기능 추가 시 기존 코드 변경 없이 가능 | |
재사용성 (Reusability) | 공통 인터페이스 및 컴포넌트 재사용률 증가 | |
관심사의 분리 (Separation of Concerns) | 도메인 로직, 인프라 로직, UI 등 계층별 책임 분리가 용이 | |
테스트 용이성 (Testability) | 의존 객체를 Mock 등으로 주입하여 단위 테스트가 쉬워짐 | |
유지보수성 향상 (Maintainability) | 변경이 국소화되어 코드 이해 및 수정이 용이 | |
설계 유연성 확보 | 전략 패턴, DI, 이벤트 기반 처리 등 다양한 설계 방식과 연계 가능 | |
⚠ 단점 | 구조적 복잡성 증가 | 객체 간 연결 관계가 분산되어 코드 구조를 직관적으로 파악하기 어려움 |
디버깅의 어려움 | 런타임에 의존성 주입, 이벤트 전파 등으로 인해 호출 추적이 복잡 | |
성능 오버헤드 | DI 컨테이너, 리플렉션, 프록시 객체 사용 등으로 인한 실행 비용 증가 가능 | |
프레임워크 종속성 증가 | 특정 IoC/DI 프레임워크 (Spring, Angular 등) 에 대한 강한 의존 위험 | |
학습 곡선 (Learning Curve) | 초보 개발자에게는 DI, 이벤트 전파, 추상화 개념이 어려울 수 있음 | |
오용 시 설계 취약점 노출 | 과도한 추상화 또는 불필요한 인터페이스 설계는 오히려 복잡도 유발 | |
초기 설정 부담 | 의존성 매핑, 설정파일 구성 등 초기 진입 비용 존재 | |
동적 흐름으로 인한 예측 어려움 | 실행 흐름이 런타임에 결정되므로 정적 분석이 어렵고 예외 발생 위치 추적이 어려움 |
- 장점은 시스템의 유연성, 테스트성, 재사용성, 구조적 품질을 대폭 향상시킴
- 단점은 시스템의 추상화 수준 증가, 학습 난이도, 디버깅/성능 관리 부담
- **현대 프레임워크 (Spring, NestJS, Angular 등)**는 이러한 장단점을 구조적으로 흡수할 수 있도록 다양한 도구 (DevTools, AOP, 트레이싱 등) 를 함께 제공
도전 과제
도전 과제 | 설명 | 해결책 |
---|---|---|
1. 코드 복잡도 증가 | 간접 호출과 추상화가 많아지며, 전반적인 코드 흐름이 복잡해짐 | 명확한 인터페이스 설계, 계층적 책임 분리, 포괄적 문서화 및 코드 리뷰 강화 |
2. 성능 저하 가능성 | 리플렉션, 프록시, 동적 주입 등의 사용으로 런타임 오버헤드 발생 | 캐싱 전략, 컴파일 타임 주입 (Dagger, NestJS), 지연 로딩 (Lazy Loading) 적용 |
3. 디버깅 난이도 | 실행 흐름이 이벤트나 DI 컨테이너에 의해 결정되어 추적이 어려움 | 구조화된 로깅 (SLF4J, Winston), APM (New Relic, Datadog), 단위별 테스트 도입 |
4. 호출 흐름 가시성 부족 | 이벤트 기반 설계나 메시지 버스 사용 시 흐름이 암시적으로 전개되어 가독성이 저하됨 | AOP 기반 트레이싱, 이벤트 로그 시각화 도구 활용 (OpenTelemetry, Zipkin 등) |
5. 학습 곡선 | DI, IoC, 이벤트 기반 패턴에 익숙하지 않은 개발자에게 진입장벽이 존재 | 페어프로그래밍, 주석 중심 설계 문서 제공, 온보딩용 샘플 프로젝트 제공 |
6. 의존성 과잉 분리 문제 | 작은 단위로 지나치게 나눈 컴포넌트는 오히려 관리 포인트를 증가시키고 오버엔지니어링 유발 | 의미 단위의 기능 그룹화, 모듈 간 책임 최소화, 필요 시 단순화 전략 적용 |
7. 프레임워크 의존성 증가 | DI/IoC 가 프레임워크에 묶여 구현될 경우 전환 비용이 커질 수 있음 | 도메인 로직을 프레임워크와 분리하고, 어댑터 패턴 등으로 종속성 차단 |
8. 테스트 복잡성 증가 | DI 및 콜백 기반 설계로 인해 테스트 환경 구성 시 모킹과 설정이 복잡해질 수 있음 | 테스트 컨테이너 활용 (Spring TestContext, Jest Mocks), 의존성 명시적 구성 |
- IoC 기반 설계는 유연성과 확장성의 장점이 크지만, 동시에 복잡성과 학습 비용, 디버깅 어려움이라는 단점이 동반된다.
- 이러한 도전 과제를 해결하기 위해선 설계 명확화, 도구 활용, 문서화, 교육 체계화가 필수이다.
- 특히 로깅/모니터링/트레이싱 도구는 실무에서 IoC 설계의 가시성과 유지보수성을 확보하는 핵심 도구이다.
분류에 따른 종류 및 유형
분류 기준 | 유형 | 설명 | 적용 사례 |
---|---|---|---|
설계 패턴 기반 | Observer | 이벤트 기반 구조에서 상태 변화 시 알림을 구독자에게 전달하는 패턴 | EventEmitter , RxJS, DOM Events |
Template Method | 알고리즘의 구조는 상위 클래스가 정의하고, 구체 구현은 하위 클래스에 위임 | Spring JdbcTemplate , React render() | |
제어 구조 방식 | Callback-based IoC | 프레임워크가 콜백 함수를 등록하고 필요 시 실행 | Express middleware, setTimeout |
Event-driven IoC | 이벤트 발생 → 핸들러 등록 기반 흐름 제어 | Node.js 이벤트 루프, NestJS | |
Injection-based IoC | 외부에서 의존성을 생성하고 객체에 주입 | Spring DI, NestJS Provider | |
Template-based IoC | 일정한 실행 순서를 정한 후 사용자 구현을 삽입하는 방식 | Java Template 패턴 | |
제어 주체 유형 | Flow Inversion | 실행 흐름 제어가 프레임워크에 의해 전환됨 | React 렌더링, Spring DispatcherServlet |
Interface Inversion | 객체가 인터페이스에 의존하고 구현체는 나중에 주입 | DIP (Dependency Inversion Principle) | |
의존성 주입 방법 | Constructor-based IoC | 생성자를 통한 의존성 주입 | @Autowired 생성자 주입 |
Setter-based IoC | 설정자 (setter) 를 통해 의존성 주입 | Spring XML DI | |
Interface-based IoC | 명시적인 인터페이스 구현을 통한 주입 | Guice, Spring | |
Annotation-driven IoC | 어노테이션으로 의존성 및 실행 흐름 제어 | Spring Boot, Jakarta CDI | |
적용 범위 기준 | Component-level IoC | 단일 컴포넌트 혹은 클래스 단위에서의 제어 역전 | React 컴포넌트, DI 단위 서비스 |
Framework-level IoC | 프레임워크가 전체 실행 제어권을 가짐 | Spring, Angular, NestJS | |
System-level IoC | 아키텍처 전반에서 이벤트와 제어 흐름을 위임 | EDA(Event-Driven Architecture), Kubernetes Operator |
실무 적용 예시
분야/기술 | 적용 방식 | 구체적 예시 |
---|---|---|
프론트엔드 | 컴포넌트 간 메시지 전달, 라이프사이클 자동 제어 | Vue 의 이벤트 버스, React useEffect , Angular ngOnInit() 등 |
웹 개발 (JS) | 이벤트 핸들러 등록에 의한 흐름 위임 | addEventListener , EventEmitter , async/await |
모바일 개발 | 시스템 호출 기반 생명주기 관리 | Android onCreate , onPause , iOS AppDelegate 메서드 |
게임 개발 | 엔진이 호출하는 구조 기반 흐름 제어 | Unity Start() , Update() 등 MonoBehaviour 메서드 |
백엔드 (Java) | 의존성 주입 (DI), 이벤트 리스닝 | Spring @Autowired , @Component , @EventListener , @Async |
Node.js | 이벤트 루프 기반 비동기 처리 | EventEmitter , 비동기 콜백, Promise , async/await |
마이크로서비스 | 이벤트 기반 아키텍처로 서비스 간 제어 분리 | Kafka, RabbitMQ 등 메시지 브로커를 통한 서비스 간 간접 통신 |
ASP.NET | 페이지/컨트롤러 생명주기 자동 호출 | Page_Load , Page_Init , MVC 컨트롤러의 OnActionExecuting 등 |
Angular | 의존성 주입 및 모듈 자동 등록 | @Injectable() , 의존성 프로바이더 설정, 컴포넌트 주입 |
Express.js | 미들웨어 체인 구조에 따른 흐름 위임 | app.use() , next() 를 활용한 요청 처리 흐름 제어 |
활용 사례
사례 1: 주문 처리 시스템에서 이메일 알림 처리
시나리오: 주문 처리 시스템에서 이메일 알림 처리
시스템 구성:
OrderService
: 주문 처리 및 이벤트 발행EmailNotificationService
: 이벤트 리스너로 동작하여 메일 발송EventDispatcher
: 이벤트 관리 및 분배
Workflow 다이어그램:
sequenceDiagram participant OrderService participant EventDispatcher participant EmailService OrderService->>EventDispatcher: emit(OrderCreated) EventDispatcher->>EmailService: on(OrderCreated) EmailService->>EmailService: sendEmail()
Workflow:
- 주문 생성 요청 →
OrderService
- 이벤트 발행:
OrderCreated
EmailService
가 해당 이벤트 수신 후 이메일 발송
역할 요약:
컴포넌트 | 역할 |
---|---|
OrderService | 주문 처리 및 이벤트 발행 |
EventDispatcher | 이벤트 라우팅 및 디스패치 |
EmailNotificationService | 후속 처리 (이메일 발송) |
사례 2: 온라인 쇼핑몰 주문 처리 시스템
시나리오: 대규모 온라인 쇼핑몰에서 주문 처리 시 여러 서비스가 협력하여 작업을 수행하는 시스템
시스템 구성 요소:
- OrderService (주문 서비스): 주문 생성 및 관리
- EventPublisher (이벤트 발행자): 시스템 이벤트 발행
- PaymentService (결제 서비스): 결제 처리
- InventoryService (재고 서비스): 재고 관리
- EmailService (이메일 서비스): 알림 이메일 발송
- EventBus (이벤트 버스): 이벤트 중개 및 라우팅
시스템 구성:
graph TB %% Frontend Layer subgraph "👤 Frontend Layer" User[사용자] end %% Service Layer subgraph "⚙️ Service Layer" OrderController[주문 컨트롤러] OrderService[주문 서비스] EventPublisher[이벤트 발행자] EventBus[이벤트 버스] PaymentService[결제 서비스] InventoryService[재고 서비스] EmailService[이메일 서비스] LoggingService[로깅 서비스] ShippingService[배송 서비스] NotificationService[알림 서비스] end %% Infra Layer (간접적 표현) subgraph "💾 Infra Layer" Database[(DB 또는 큐 시스템)] end %% 흐름 정의 User -->|"1.주문 요청"| OrderController OrderController -->|"2.주문 처리"| OrderService OrderService -->|"3.이벤트 발행"| EventPublisher EventPublisher -->|"4.주문 생성 이벤트"| EventBus EventBus -->|"5.결제 처리"| PaymentService EventBus -->|"6.재고 차감"| InventoryService EventBus -->|"7.이메일 발송"| EmailService EventBus -->|"8.로깅"| LoggingService PaymentService -->|결제 완료 이벤트| EventBus InventoryService -->|재고 차감 이벤트| EventBus EventBus -->|배송 준비 이벤트| ShippingService ShippingService -->|배송 시작 이벤트| EventBus EventBus -->|알림 발송| NotificationService %% Infra 연계 표현 PaymentService --> Database InventoryService --> Database LoggingService --> Database
Hollywood Principle 의 역할:
- 이벤트 기반 아키텍처: 각 서비스는 직접 호출하지 않고 이벤트를 통해 통신
- 느슨한 결합: 서비스 간 직접적인 의존성 없이 독립적 운영
- 확장성: 새로운 서비스 추가 시 기존 코드 변경 없이 이벤트 구독만 추가
- 비동기 처리: 블로킹 없이 여러 작업을 동시에 처리
Workflow:
- 사용자가 주문 요청을 보냄
- 주문 컨트롤러가 요청을 받아 주문 서비스에 전달
- 주문 서비스가 " 주문 생성 " 이벤트를 이벤트 버스에 발행
- Hollywood Principle 적용: 이벤트 버스가 등록된 서비스들을 호출
- 각 서비스가 독립적으로 작업 수행 (결제, 재고 차감, 이메일 발송)
- 작업 완료 후 결과를 다시 이벤트로 알림
- 최종적으로 사용자에게 주문 완료 응답 전송
sequenceDiagram participant User as 사용자 participant Controller as 주문 컨트롤러 participant OrderSvc as 주문 서비스 participant EventBus as 이벤트 버스 participant PaymentSvc as 결제 서비스 participant InventorySvc as 재고 서비스 participant EmailSvc as 이메일 서비스 participant ShippingSvc as 배송 서비스 Note over User,ShippingSvc: 주문 처리 워크플로우 User->>Controller: 1. 주문 요청 (POST /orders) Controller->>OrderSvc: 2. 주문 생성 요청 OrderSvc->>OrderSvc: 3. 주문 데이터 검증 및 저장 OrderSvc->>EventBus: 4. "OrderCreated" 이벤트 발행 Note over EventBus: Hollywood Principle 적용점:<br/>"우리가 필요한 서비스들을 호출할게요!" par 병렬 처리 (Parallel Processing) EventBus->>PaymentSvc: 5a. 결제 처리 콜백 호출 PaymentSvc->>PaymentSvc: 결제 API 호출 PaymentSvc->>EventBus: "PaymentCompleted" 이벤트 and EventBus->>InventorySvc: 5b. 재고 차감 콜백 호출 InventorySvc->>InventorySvc: 재고 확인 및 차감 InventorySvc->>EventBus: "InventoryUpdated" 이벤트 and EventBus->>EmailSvc: 5c. 주문 확인 이메일 콜백 호출 EmailSvc->>EmailSvc: 이메일 템플릿 생성 및 발송 EmailSvc->>EventBus: "EmailSent" 이벤트 end Note over EventBus: 모든 이벤트 완료 확인 EventBus->>ShippingSvc: 6. 배송 준비 콜백 호출 ShippingSvc->>ShippingSvc: 배송 라벨 생성 ShippingSvc->>EventBus: "ShippingPrepared" 이벤트 EventBus->>Controller: 7. 주문 처리 완료 알림 Controller->>User: 8. 주문 완료 응답 (200 OK) Note over User,ShippingSvc: 전체 프로세스에서 Hollywood Principle:<br/>각 서비스는 직접 호출하지 않고<br/>이벤트 버스가 필요할 때 호출
실무에서 효과적으로 적용하기 위한 고려사항 및 주의할 점
구분 | 고려사항 | 설명 | 권장사항 |
---|---|---|---|
설계 원칙 | 적절한 추상화 수준 유지 | 과도한 추상화는 복잡도만 높이고, 추상화 부족은 재사용성을 낮춤 | 핵심 도메인만 추상화, 구체 구현은 필요시 계층화 적용 |
인터페이스 | 명확한 계약 정의 | 소비자와 제공자 간의 인터페이스는 역할에 맞게 분리되어야 함 | 단일 책임 원칙 (SRP), 인터페이스 분리 원칙 (ISP) 적용 |
구현 구조 | IoC/DI 구조의 일관성 | 프레임워크/컨테이너에 따라 설계 표준과 구현 패턴이 달라질 수 있음 | 코드 스타일 가이드 문서화, 코드 리뷰 프로세스 강화 |
컴포넌트 책임 | 모듈 간 책임 명확화 | 이벤트/콜백/의존성 흐름이 명확하지 않으면 추적과 유지보수가 어려움 | 이벤트 네이밍 컨벤션 정립, 도메인 중심 핸들러 설계 |
테스트 전략 | 테스트 용이성 확보 | DI 를 적용해도 테스트 전략이 미흡하면 결합도를 낮추는 이점이 사라짐 | Mock/Stub/Fake 등 Test Double 전략 활용 |
에러 처리 | 장애 전파 방지 | 느슨한 결합 구조에서는 한 컴포넌트 실패가 전체 장애로 이어질 수 있음 | Circuit Breaker, Retry, Timeout 설정 적용 |
흐름 추적 | 이벤트 및 제어 흐름 가시성 확보 | 비동기 구조에서는 흐름 파악이 어려워 디버깅, 운영에 어려움 발생 | APM, 구조화 로깅, 시퀀스 다이어그램 기반 문서화 |
문서화 | 호출 구조 및 의사결정 기록 | DI 구조에서는 흐름이 코드로 드러나지 않으므로 시각적 문서화 필요 | 아키텍처 다이어그램, 이벤트 카탈로그, 기술 의사결정 기록 (ADR) 관리 |
모니터링 | 시스템 관측 가능성 (Observability) | 문제 발생 원인을 빠르게 식별할 수 있는 설계 필요 | 로그 집계 (ELK), 분산 추적 (Jaeger, OpenTelemetry), 메트릭 수집 (Prometheus) |
최적화하기 위한 고려사항
카테고리 | 고려사항 | 설명 | 권장사항 |
---|---|---|---|
이벤트 처리 | 이벤트 병목 방지 | 하나의 이벤트에 과도한 리스너가 연결될 경우 처리 지연 발생 가능 | 이벤트 큐, 메시지 브로커, 비동기 배치 처리 도입 |
이벤트 순서 보장 | 이벤트 핸들러가 비동기일 경우 순서가 꼬이는 문제 발생 | Kafka Partition Key, Event Sourcing 시간 정렬 사용 | |
불필요한 이벤트 제한 | 남용 시 코드 복잡도 및 자원 낭비 유발 | 필요 이벤트만 정의, Consumer 수 제한, 이벤트 카탈로그 관리 | |
콜백/비동기 | 콜백 체인 최적화 | 깊은 콜백 체인은 디버깅/유지보수 어려움 | Promise/async-await, 콜백 분리, 체인 길이 제한 |
비동기 처리 최적화 | 비동기 병렬 처리가 오히려 성능 저하로 이어질 수 있음 | 스레드 풀 크기 조정, 논블로킹 I/O (예: Netty, Reactor) | |
DI 구성 요소 | DI 오버헤드 관리 | DI 컨테이너 자체의 초기화 비용, 리플렉션 성능 이슈 존재 | JIT 대신 AOT 컴파일 사용 (Spring Native 등), 런타임 캐싱 |
불필요한 추상화 제거 | 과도한 추상화는 불필요한 계층 및 호출 비용 유발 | 실제 변경 가능성이 있는 부분만 추상화 | |
의존성 범위 최소화 | DI 범위가 넓을수록 초기화 시간과 메모리 사용 증가 | DI 타겟을 단일 책임 단위로 분리, 프로토타입보다 싱글톤 활용 | |
메모리 관리 | GC 친화적 구조 설계 | 이벤트 리스너 및 DI 객체의 참조 누락 시 GC 대상 제외 | WeakReference 사용, 리스너 해제 필수, @PreDestroy 등으로 정리 수행 |
리플렉션 | 리플렉션 최소화 | DI 프레임워크의 리플렉션 사용은 런타임 성능 저하 원인 | 메타프로그래밍 최소화, 런타임 코드 생성 회피, final 타입 활용 |
직렬화/캐싱 | 효율적인 데이터 직렬화 | 느린 JSON 직렬화는 응답 지연 유발 | Protobuf, Avro 등 이진 직렬화 적용, GZIP 압축 |
런타임/컴파일 캐싱 | 동일 의존성/이벤트 반복 생성 시 오버헤드 발생 | CDI 캐시, Context 객체 재사용, 인스턴스 풀링 적용 | |
모니터링/분석 | 성능 병목 지점 파악 | 병목 발생 시 위치 추적이 어려운 구조 | APM (New Relic, Datadog), 분산 추적 (OpenTelemetry, Jaeger) 도입 |
주제와 관련하여 주목할 내용
분류 | 항목 | 설명 |
---|---|---|
핵심 원칙 | Hollywood Principle | “Don’t call us, we’ll call you”—프레임워크가 제어 흐름을 관리 |
의존성 역전 원칙 (DIP) | 고수준 모듈이 저수준 모듈에 의존하지 않고 추상화에 의존하도록 설계 | |
단일 책임 원칙 (SRP) | 컴포넌트가 하나의 책임만 가지도록 하여 유지보수성과 확장성 확보 | |
디자인 패턴 | Strategy Pattern | 실행 알고리즘을 동적으로 변경 가능하게 하여 유연한 구조 제공 |
Command Pattern | 요청을 객체로 캡슐화해 실행을 지연하거나 큐잉/로깅 등 가능 | |
Observer Pattern | 이벤트 발생 시 리스너에게 자동 알림, 느슨한 결합 구현 | |
Template Method Pattern | 알고리즘의 틀은 상위 클래스에서, 세부 로직은 하위 클래스에 위임 | |
아키텍처 | 마이크로서비스 아키텍처 | 서비스 간 통신을 이벤트 기반 메시징으로 처리하여 IoC 구현 |
육각형 아키텍처 (Hexagonal) | 포트 - 어댑터 구조로 외부와의 의존성 방향을 명확히 분리 | |
프레임워크 | Spring IoC Container | 의존성 주입 및 생명주기 제어를 지원하는 Java 기반 IoC 프레임워크 |
Angular DI | 계층적 주입 트리 기반의 DI 시스템을 제공하는 프론트엔드 프레임워크 | |
기술/언어 | AOP (Aspect-Oriented Programming) | 횡단 관심사 분리로 로깅/보안/트랜잭션 등의 IoC 기반 처리 가능 |
Reactive Programming | 비동기 스트림 기반으로 이벤트/데이터 흐름을 조율하는 패러다임 | |
JavaScript Event Loop | 비동기 콜백 기반 IoC 처리 메커니즘 (단일 스레드 환경에서의 제어 역전 예시) | |
C# Delegates / Events | 형식 안전한 콜백 구조로 이벤트 기반 프로그래밍 가능 | |
설계 목적 | 느슨한 결합 | 객체 간 독립성 강화로 유지보수성/확장성 확보 |
의존성 부패 방지 (Dependency Rot) | 과도하게 얽힌 의존성 구조를 추상화와 제어 분리로 해소 |
하위 주제별 추가 학습 필요 내용
카테고리 | 주제 | 간략 설명 |
---|---|---|
디자인 패턴 | Observer Pattern | 이벤트 알림 구조로 느슨한 결합 구현 |
Template Method Pattern | 알고리즘 골격은 상위에서, 세부는 하위 클래스에 위임 | |
Strategy Pattern | 런타임 알고리즘 교체 가능, 유연한 설계 구조 | |
IoC / DI | IoC (Inversion of Control) | 제어 흐름을 프레임워크가 담당, Hollywood Principle 의 핵심 |
DI (Dependency Injection) | 외부에서 의존 객체를 주입, 테스트/유지보수 용이 | |
설계 원칙 | SOLID 원칙 (특히 DIP, SRP) | 의존성 역전 원칙, 단일 책임 원칙 등 IoC 와 밀접한 설계 원칙 |
Law of Demeter (최소 지식 원칙) | 컴포넌트 간 결합도 최소화, 간접 호출 지양 | |
아키텍처 패턴 | 이벤트 소싱 (Event Sourcing) | 모든 상태 변경을 이벤트로 저장, 감사/복원 가능 |
CQRS (Command Query Responsibility Segregation) | 읽기/쓰기 분리로 시스템 확장성과 최적화 구현 | |
플러그인/확장성 설계 | 플러그인 아키텍처 | 동적으로 기능 추가 가능한 구조, 인터페이스 기반 확장 |
이벤트 시스템 | 도메인 이벤트를 중심으로 비동기/확장 가능한 시스템 구축 | |
테스트 전략 | Mock / Stub / Spy | IoC 환경에서의 의존성 대체 및 단위 테스트 구현 기법 |
동시성 모델 | Actor Model | 메시지를 주고받는 방식으로 동시성 제어, 고립성 유지 |
CSP (Communicating Sequential Processes) | 채널 기반 통신 (Go, Kotlin Coroutine 등에서 활용) | |
함수형 프로그래밍 | 고차 함수 (Higher-Order Functions) | 함수를 매개변수/리턴값으로 사용하는 추상화 기법 |
Reactive Extensions (Rx, Reactor 등) | 비동기 데이터 스트림과 옵저버블 기반 이벤트 처리 모델 |
관련 분야별 추가 학습 내용
카테고리 | 주제 | 설명 |
---|---|---|
웹 개발 | Webhook 아키텍처 | 외부 시스템과의 통합 시 HTTP 기반 이벤트 푸시 방식 |
Server-Sent Events (SSE) | 서버에서 클라이언트로 단방향 실시간 데이터 전송 방식 | |
클라우드 | Serverless 아키텍처 | 이벤트 기반 함수 실행 (ex. AWS Lambda), 자동 확장 및 비용 최적화에 효과적 |
AWS 기반 이벤트 아키텍처 | EventBridge, Lambda, SQS 조합으로 서버리스 이벤트 처리 구현 | |
데이터베이스 | Database Triggers | INSERT/UPDATE/DELETE 시 자동 실행되는 DB 수준의 콜백 기능 |
Change Data Capture (CDC) | 데이터 변경을 추적하여 외부 시스템으로 이벤트 발행 (ex. Debezium, Kafka Connect) | |
모바일 개발 | iOS Delegation Pattern | 델리게이트를 활용한 ViewController 간의 통신 구조 (Swift/Objective-C) |
Android Lifecycle Callbacks | 생명주기 이벤트에 따른 콜백 메서드 활용 (onCreate , onResume 등) | |
게임 개발 | Entity-Component-System (ECS) | 데이터 중심, 컴포넌트 기반 아키텍처로 유연하고 확장성 높은 게임 로직 구성 |
Event Bus Pattern | 객체 간 메시지를 느슨하게 전달하는 게임 이벤트 처리 방식 | |
DevOps | GitOps Workflow | Git 저장소의 이벤트 기반으로 자동화된 CI/CD 파이프라인 실행 |
Infrastructure as Code (IaC) | Terraform, Pulumi 등으로 선언적으로 인프라를 관리하고 자동화 | |
소프트웨어 설계 | 레이어드 아키텍처 | 표현, 애플리케이션, 도메인, 인프라 계층으로 구성된 구조에서 IoC 적용 지점 분석 |
결합도와 응집도 | 컴포넌트 간 의존성 최소화 (Loose Coupling) 와 책임 집중 (High Cohesion) 설계 원칙 | |
코드 품질 | 의존성 분석 | 의존성 그래프 시각화 및 모듈간 결합도 분석 (ex. SonarQube, CodeScene) |
성능 최적화 | DI 컨테이너 성능 최적화 | DI 사용에 따른 메모리/속도 이슈 관리 (AOT 컴파일, Lazy Loading 등) |
용어 정리
핵심 원칙 및 설계 철학
용어 | 설명 |
---|---|
Hollywood Principle (헐리우드 원칙) | “Don’t call us, we’ll call you”—제어 흐름을 프레임워크가 담당. |
Inversion of Control (IoC, 제어의 역전) | 실행 흐름 제어권을 프레임워크 등 상위 컴포넌트로 전환하는 설계 원칙 |
Dependency Injection (DI, 의존성 주입) | 의존성을 외부에서 주입하여 결합도를 낮추는 IoC 구현 방식 |
느슨한 결합 (Loose Coupling) | 컴포넌트 간 의존성이 낮아 변경에 강한 구조 |
높은 응집도 (High Cohesion) | 한 모듈이 명확하고 관련된 책임만 갖도록 하는 설계 |
Law of Demeter (최소 지식 원칙) | 다른 객체 내부 구조를 몰라도 작동하는 설계 |
디자인 패턴 및 구조 전략
용어 | 설명 |
---|---|
Observer / Template / Strategy 패턴 | 제어 흐름 역전이 적용되는 대표적인 디자인 패턴 |
CQRS | Command 와 Query 를 분리해 읽기/쓰기 책임을 분리하는 아키텍처 패턴 |
Event Sourcing | 상태 저장 대신 이벤트 시퀀스로 시스템 상태를 재구성하는 방식 |
Choreography | 서비스 간 이벤트 기반 비중앙 통합 방식 |
Orchestration | 중앙 조정자가 서비스 호출을 순서대로 관리하는 통합 방식 |
Service Locator | 의존 객체를 찾기 위한 전역 접근점 제공 패턴 (IoC 대안이나 결합도 높음) |
비동기/이벤트 기반 프로그래밍
용어 | 설명 |
---|---|
Callback Hell | 중첩된 콜백 구조로 인해 가독성 및 유지보수성이 낮아지는 문제 |
Event Loop | JavaScript 등 단일 스레드 환경에서 비동기 작업을 처리하는 메커니즘 |
Reactive Streams | 비동기 데이터 흐름을 처리하기 위한 스트림 표준 |
Backpressure | 생산자와 소비자 간 속도 불균형을 조절하는 흐름 제어 메커니즘 |
Fluent Interface | 메서드 체이닝 기반의 유창한 API 설계 방식 |
실패 내성 및 안정성 설계
용어 | 설명 |
---|---|
Circuit Breaker | 연속 실패 시 호출 차단으로 시스템을 보호하는 패턴 |
Retry | 실패한 작업을 일정 횟수 재시도하는 실패 복구 전략 |
보상 트랜잭션 (Compensating Transaction) | 실패한 작업의 반대 작업을 통해 데이터 일관성을 복구하는 전략 |
Idempotency | 동일 작업을 여러 번 실행해도 결과가 동일한 특성 (중복 처리 방지) |
DLQ (Dead Letter Queue) | 실패 메시지를 별도로 격리하여 후속 처리용으로 보관하는 큐 |
IoC 인프라 및 실무 관련 개념
용어 | 설명 |
---|---|
IoC Container | 객체 생성, 의존성 주입, 생명주기 관리 등을 담당하는 컨테이너 컴포넌트 |
Aspect Weaving | AOP(관점 지향 프로그래밍) 에서 관심사를 핵심 로직에 삽입하는 처리 과정 |
의존성 부패 (Dependency Rot) | 과도한 의존성 확장 및 모듈 간 결합 증가로 구조가 무너지는 현상 |
참고 및 출처
핵심 개념 (Hollywood Principle / IoC / DI)
- Inversion of Control – Wikipedia
- Martin Fowler’s Bliki: Inversion Of Control
- Hollywood Principle – DevIQ
- The Hollywood Principle – DZone
- Hollywood Principle in Software Design – Medium
- What is the Hollywood Principle? – Stack Overflow
- The Hollywood Principle – Matthew Mead Blog
- Design Patterns Explained – Dependency Injection – Stackify
- Three Design Patterns That Use Inversion of Control – SitePoint
- Inversion of Control (IoC) Design Principle – LinkedIn
프레임워크 / 이벤트 시스템 실무 예시
- Spring Events – Baeldung
- Spring – Event Handling – GeeksforGeeks
- Spring Cloud Stream Error Handling Guide
클라우드 / 메시징 / 아키텍처
- Event-Driven Architecture Best Practices – AWS Docs
- Handling Errors in Kafka Consumers – Confluent
- Dead Letter Queues – RabbitMQ
- Martin Fowler – Event-Driven Architecture
기타 참고 아티클 및 커뮤니티 자료
- DEV Community: Code Smell 271 – The Hollywood Principle
- Peter Miľovčík – Hollywood Principle and Patterns
- HackerNoon – The Hollywood Principle의 설계적 의미
- 기계인간 John Grib: 헐리우드 원칙과 의존성 부패
- Doublem.org – 헐리우드 원칙의 실무적 의미