Facade: 모바일 아키텍처에서 퍼사드 패턴의 기초

저자: IT Sectr 게시일: 2026-02-18 읽는 시간: 9 분

Facade는 클래스의 복잡한 하위 시스템에 단순화된 인터페이스를 제공하는 구조적 디자인 패턴입니다. 모바일 개발에서 Facade는 네트워크, 데이터베이스 및 분석과의 상호작용을 숨기는 Service Layer 또는 UseCase로 가장 자주 구현됩니다. Martin Fowler(Patterns of Enterprise Application Architecture, 2003)에 따르면 Facade는 서비스 계층을 구성하는 핵심 패턴 중 하나입니다.

핵심 요점

  • Facade — 클래스, 라이브러리 또는 프레임워크의 복잡한 시스템에 간단한 인터페이스를 제공하는 구조적 패턴
  • Service Layer — API, 캐시 및 분석을 UI에서 숨기는 모바일 아키텍처의 Facade 구현
  • Facade는 하위 시스템을 숨기지 않습니다 — 클라이언트는 필요할 때 직접 액세스할 수 있습니다
  • Clean Architecture의 UseCase — 단일 비즈니스 시나리오를 조정하는 Facade의 변형
  • Facade vs Adapter: Facade는 인터페이스를 단순화하고 Adapter는 한 인터페이스를 다른 인터페이스로 변환합니다

Facade 패턴이란 무엇인가요?

Facade는 하위 시스템 인터페이스 그룹에 통합된 인터페이스를 제공하는 구조적 패턴입니다. 하위 시스템의 사용을 단순화하는 고수준 인터페이스를 정의합니다. Facade는 새로운 기능을 추가하지 않습니다. 기존 구성 요소를 조정하고 상호작용의 복잡성을 클라이언트로부터 숨깁니다.

Kotlin
// 복잡한 하위 시스템
class AuthApi {
    suspend fun login(email: String, pass: String): TokenResponse
}

class UserDao {
    suspend fun saveUser(user: UserEntity)
    suspend fun getUser(id: Long): UserEntity?
}

class AnalyticsTracker {
    fun track(event: String, params: Map)
}

// Facade — UI를 위한 간단한 인터페이스
class AuthService(
    private val api: AuthApi,
    private val dao: UserDao,
    private val analytics: AnalyticsTracker
) {
    suspend fun loginUser(email: String, password: String): Result {
        return runCatching {
            val token = api.login(email, password)
            val user = User(token.userId, email, token.accessToken)
            dao.saveUser(user.toEntity())
            analytics.track("login_success", mapOf("method" to "email"))
            user
        }
    }
}

AuthService는 AuthApi, UserDao 및 AnalyticsTracker를 ViewModel로부터 숨기는 Facade입니다. UI는 API, 데이터베이스 및 분석에 대한 세 개의 개별 요청 대신 loginUser(email, password)를 호출합니다. 이는 결합도를 낮춥니다. 내일 AuthApi가 FirebaseAuth가 되거나 UserDao가 Room으로 마이그레이션되면 Facade만 변경되고 UI는 변경되지 않습니다.

모바일 아키텍처의 Facade: Service Layer

Service Layer는 모바일 애플리케이션에서 Facade의 일반적인 구현입니다. 비즈니스 로직과 계층 간의 조정을 캡슐화합니다. Android에서 Service Layer는 종종 UseCase(Clean Architecture)를 통해 구현되고 iOS에서는 Manager 또는 Service 프로토콜을 통해 구현됩니다.

구성 요소 하위 시스템에서의 역할 Facade가 숨기는 것
AuthApi 서버에 대한 네트워크 요청 요청 형식, endpoint, HTTP 오류 처리
UserDao 토큰의 로컬 저장 데이터베이스 스키마, SQL 쿼리, 마이그레이션
AnalyticsTracker 분석 이벤트 전송 Firebase/AppMetrica SDK, 이벤트 형식
NetworkMonitor 네트워크 가용성 확인 ConnectivityManager, BroadcastReceiver

AuthService는 네 가지 구성 요소를 모두 결합합니다. ViewModel은 내부에서 네트워크 요청, 데이터베이스 쓰기, 추적 및 네트워크 확인이 발생하는지 모른 채 하나의 메서드를 호출합니다. 테스트 시 AuthService를 mock으로 대체하여 실제 구성 요소와의 통합 없이 전체 인증 로직을 검증할 수 있습니다.

Facade vs Adapter vs Mediator

Facade, Adapter 및 Mediator는 구조적 패턴이지만 서로 다른 문제를 해결합니다. 세 가지 모두 중재자 객체를 도입하기 때문에 종종 혼동됩니다. 모바일 애플리케이션 예시로 차이점을 분석해 보겠습니다.

측면 Facade Adapter Mediator
목적 하위 시스템 인터페이스 단순화 인터페이스 변환 구성 요소 결합도 감소
방향 하나의 인터페이스 → 하위 시스템 클라이언트 → Adaptee N개 구성 요소 ↔ Mediator
인터페이스 변경 새롭고 단순화된 것을 생성 기존 것을 변환 변경하지 않고 조정
하위 시스템이 패턴을 알고 있나요? 아니요 아니요 예, Mediator를 통해 통신
모바일 개발에서의 예 UseCase / Service Layer RecyclerView.Adapter iOS의 Coordinator

Facade는 하위 시스템을 숨기지 않습니다. 클라이언트는 필요할 때 AuthApi에 직접 액세스할 수 있습니다. Adapter는 반드시 Adaptee의 인터페이스를 변경합니다. Mediator는 서로를 알지 못할 수 있는 많은 객체 간의 복잡한 상호작용을 조정합니다.

Android용 Kotlin에서 Facade 구현

Facade 구현은 Clean Architecture를 사용하는 Android용 Kotlin에서 각 비즈니스 시나리오의 진입점으로 UseCase를 사용합니다. UseCase는 리포지토리, 매퍼 및 기타 종속성을 UI 계층에서 숨기는 Facade입니다.

Kotlin
// Repository도 Facade이지만 더 낮은 수준입니다
class UserRepositoryImpl(
    private val local: UserLocalDataSource,
    private val remote: UserRemoteDataSource,
    private val mapper: UserMapper
) : UserRepository {
    override suspend fun getUserProfile(id: String): UserProfile {
        val cached = local.getUser(id)
        if (cached != null && !cached.isStale) {
            return mapper.toProfile(cached)
        }
        val dto = remote.fetchUser(id)
        val entity = mapper.toEntity(dto)
        local.saveUser(entity)
        return mapper.toProfile(entity)
    }
}

// UseCase — 비즈니스 시나리오를 위한 Facade
class LoadUserProfileUseCase(
    private val repo: UserRepository,
    private val analytics: AnalyticsTracker
) {
    suspend operator fun invoke(userId: String): Result {
        return runCatching {
            val profile = repo.getUserProfile(userId)
            analytics.track("profile_loaded", mapOf("user_id" to userId))
            profile
        }
    }
}

LoadUserProfileUseCase는 프로필 로딩 시나리오를 위한 Facade입니다. 캐싱 로직(local → remote), DTO → Entity → Profile 매핑 및 분석 추적을 숨깁니다. ViewModel은 invoke(userId)를 호출하고 준비된 UserProfile 또는 오류를 받습니다. UseCase는 리포지토리를 mock 객체로 대체하여 격리된 상태로 테스트할 수 있습니다.

iOS용 Swift에서 Facade 구현

iOS의 Facade는 종종 Manager 또는 Service로 구현됩니다. Android와 달리 iOS는 Facade 인터페이스를 정의하기 위해 프로토콜을 사용하므로 테스트에서 구현을 쉽게 교체할 수 있습니다. 미디어 작업을 위한 Facade를 살펴보겠습니다. 로딩, 캐싱 및 표시입니다.

Swift
protocol MediaServiceProtocol {
    func loadImage(from url: URL) async -> Result<UIImage, Error>
}

final class MediaService: MediaServiceProtocol {
    private let cache: ImageCache
    private let downloader: ImageDownloader
    private let decoder: ImageDecoder
    
    func loadImage(from url: URL) async -> Result<UIImage, Error> {
        // 1. 캐시 확인
        if let cached = cache.image(for: url) {
            return .success(cached)
        }
        // 2. 데이터 로드
        let result = await downloader.download(from: url)
        guard case let .success(data) = result else {
            return .failure(MediaError.downloadFailed)
        }
        // 3. 디코드
        guard let image = decoder.decode(data) else {
            return .failure(MediaError.decodeFailed)
        }
        // 4. 캐시에 저장
        cache.setImage(image, for: url)
        return .success(image)
    }
}

MediaService는 3단계 프로세스를 캡슐화합니다. 캐시 → 로드 → 디코드입니다. UI는 ImageCache, URLSession 및 ImageDecoder를 관리하는 대신 하나의 메서드 loadImage(from:)를 호출합니다. 테스트 시 MediaServiceProtocol을 실제 로딩 없이 미리 설정된 이미지를 반환하는 mock으로 대체할 수 있습니다.

Facade 사용 시 흔한 실수

실수는 Facade 설계에서 그 장점을 무효화합니다. 단순화 대신 전체 시스템이 의존하는 God Object가 생깁니다. 세 가지 주요 문제를 분석해 보겠습니다.

God Facade — 너무 많은 책임

하나의 Facade가 인증, 프로필 로딩, 메시지 전송 및 동기화 메서드를 포함하면 이는 God Object입니다. 징후: 한 클래스에 15개 이상의 공개 메서드. 해결책: 책임 영역별로 여러 개의 특화된 Facade로 분할하세요 — AuthService, ProfileService, MessagingService.

하위 시스템 세부 정보가 유출되는 Facade

Facade가 하위 시스템에 특정한 유형(예: FirebaseUser 또는 RealmObject)을 반환하면 클라이언트는 여전히 특정 구현에 묶여 있습니다. 해결책: Facade는 자체 유형(data class / struct)만 반환하고 클라이언트를 하위 시스템 세부 정보에서 완전히 추상화해야 합니다.

유일한 진입점으로서의 Facade

Facade가 하위 시스템에 대한 직접 액세스를 금지하면 병목 현상이 됩니다. 때로는 클라이언트가 하위 시스템의 특정 메서드가 필요하며 Facade를 통과하게 하는 것은 불필요합니다. Facade는 엄격한 게이트키퍼가 되어서는 안 됩니다. 편리한 인터페이스를 제공하지만 구성 요소에 대한 직접 액세스를 차단하지 않습니다.

자주 묻는 질문

Facade와 Proxy의 차이점은 무엇인가요?

Facade는 하위 시스템에 단순화된 인터페이스를 제공하며 종종 새로운 메서드 집합을 만듭니다. Proxy는 원래 객체와 동일한 인터페이스를 유지하지만 액세스 제어 또는 지연 로딩을 추가합니다. Facade는 단순화를 위한 것이고 Proxy는 제어를 위한 것입니다.

Facade는 Service Layer와 같은 것인가요?

Service Layer는 애플리케이션 아키텍처 수준에서 Facade 패턴의 구현입니다. UI와 비즈니스 로직 사이의 경계를 정의하고 서비스 구현 세부 정보를 숨깁니다. Android에서 Service Layer는 종종 UseCase를 통해 구현되고 iOS에서는 Manager 또는 Service 프로토콜을 통해 구현됩니다.

Facade는 언제 God Object가 되나요?

God Facade는 한 클래스가 여러 무관한 하위 시스템의 책임을 맡을 때 발생합니다. 징후: 15개 이상의 공개 메서드, 다른 도메인의 메서드(인증 + 결제 + 알림), 테스트하기 어려운 클래스(10개 이상의 종속성). 해결책: 도메인 Facade로 분할하세요.

작은 애플리케이션에 Facade가 필요한가요?

1~2개 화면의 애플리케이션에서는 Facade가 불필요합니다. UI에서 API와 데이터베이스를 직접 호출하는 것이 더 간단하고 명확합니다. Facade는 5개 이상의 화면과 3개 이상의 하위 시스템에서 효과를 발휘합니다. 소규모 및 중간 규모 프로젝트에서는 추가 UseCase 래퍼 없이 Repository만 유일한 Facade 계층으로 충분합니다.

Facade를 사용하는 코드는 어떻게 테스트하나요?

Facade는 테스트를 단순화합니다. 전체 하위 시스템을 단일 mock 객체로 대체하기 때문입니다. 세 가지 구성 요소(네트워크 + 데이터베이스 + 분석)를 mock하는 대신 하나의 Facade를 mock하면 충분합니다. Swift에서는 이를 위해 프로토콜이 사용되고 Kotlin에서는 인터페이스가 사용됩니다. Facade는 구성 요소 조정을 검증하는 통합 테스트에도 편리합니다.

요약

  • Facade — 복잡한 하위 시스템에 간단한 인터페이스를 제공하는 구조적 패턴
  • Service LayerUseCase — 모바일 아키텍처에서 Facade의 일반적인 구현
  • Facade는 하위 시스템을 숨기지 않습니다. 클라이언트는 필요할 때 구성 요소에 직접 액세스할 수 있습니다
  • Facade vs Adapter: Facade는 단순화하고 Adapter는 변환합니다; Facade vs Mediator: Facade는 단방향, Mediator는 양방향
  • God Facade — 안티패턴: 한 클래스에 15개 이상의 메서드가 있으면 단일 책임 위반을 나타냅니다
  • Facade를 위한 프로토콜/인터페이스는 필수입니다 — 하위 시스템을 mock으로 테스트하는 유일한 방법입니다
  • 권장 사항: 5개 이상의 화면과 3개 이상의 하위 시스템에서 Facade를 도입하세요. 소규모 프로젝트에서는 Repository로 충분합니다

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기