미들웨어는 애플리케이션의 주요 로직 전후에 데이터를 처리하고, 횡단 관심사를 비즈니스 코드로부터 분리하는 중간 소프트웨어 계층입니다. Redux (2026)에 따르면, 미들웨어는 중앙 집중식 처리를 통해 로깅 및 인증 코드 중복을 40%까지 줄입니다. Redux 미들웨어는 전형적인 예이지만, 이 패턴은 Ktor Client, Bloc, Express.js 및 Dio에서 더 광범위하게 사용됩니다.
핵심 포인트
미들웨어는 두 시스템 구성 요소 사이에 위치하여 데이터를 가로채고 처리한 후 대상 구성 요소로 전달하는 소프트웨어 계층입니다. 모바일 개발에서 미들웨어는 세 가지 주요 컨텍스트에서 사용됩니다: 상태 관리(Redux, Bloc), HTTP 통신(Ktor Client, Dio), 이벤트 처리(EventBus, NotificationCenter). 핵심 가치는 애플리케이션의 도메인 로직으로부터 횡단 관심사의 분리(로깅, 인증, 분석)입니다. 각 화면에 분석 호출을 추가하는 대신 미들웨어가 중앙에서 처리합니다.
미들웨어는 Pipe and Filter 패턴을 구현합니다: 각 미들웨어 구성 요소가 데이터를 수신하여 처리하고 체인의 다음 링크로 전달합니다. 미들웨어 연결 순서가 처리 순서를 결정합니다 — 첫 번째 미들웨어는 원시 데이터를 수신하고, 마지막 미들웨어는 대상 핸들러에 전달합니다. JetBrains (2026)에 따르면, 이 아키텍처는 기존 코드를 변경하지 않고 미들웨어를 추가하거나 제거할 수 있어 테스트 및 실험적 모듈의 A/B 테스트를 간소화합니다.
미들웨어는 일반적인 패턴이고, Interceptor는 HTTP를 위한 특수 케이스입니다. 미들웨어는 모든 데이터 흐름(Redux의 actions, Bloc의 이벤트, Ktor의 HTTP 요청)에서 작동합니다. Interceptor는 항상 네트워크 계층에 바인딩되며 Request/Response만 처리합니다. 이 차이점을 이해하면 올바른 추상화를 선택하는 데 도움이 됩니다: 사용자 작업 로깅에는 미들웨어, 헤더 추가에는 Interceptor. 대규모 프로젝트에서는 두 패턴이 공존하는 경우가 많으며, 미들웨어가 상태를 관리하고 Interceptor가 HTTP 통신을 처리합니다.
Redux 미들웨어는 reducer에 도달하기 전에 각 dispatch action을 가로챕니다. 이를 통해 actions 로깅, Redux Thunk 또는 Redux Saga를 통한 비동기 요청 실행, action 수정 또는 조건부 취소가 가능합니다. 각 미들웨어는 store(상태 접근), next(다음 미들웨어 또는 reducer 참조) 및 action을 수신하고, action을 전달할지, 수정할지, 차단할지 결정합니다.
Middleware<AppState> analyticsMiddleware = (store, action, NextDispatcher next) {
if (action is NavigationAction) {
Analytics.logEvent(action.screenName);
}
return next(action);
};
final store = Store<AppState>>(
reducer,
initialState,
middleware: [analyticsMiddleware]
);
Flutter Redux용 Dart의 analyticsMiddleware 예제. 미들웨어는 모든 NavigationAction을 가로채서 화면 이름을 분석 시스템에 기록하고 next(action)을 호출하여 체인을 계속합니다. next가 호출되지 않으면 action이 reducer에 도달하지 않습니다 — 이를 통해 조건부 탐색을 구현하거나 원치 않는 actions을 차단할 수 있습니다. 배열의 미들웨어 순서가 처리 순서를 결정합니다.
Redux Thunk는 action 객체뿐만 아니라 함수도 dispatch할 수 있는 미들웨어입니다. 함수는 dispatch와 getState를 수신하고, 비동기 작업(HTTP 클라이언트를 통한 API 요청, DB 읽기)을 수행하고 완료 시 일반 actions을 dispatch할 수 있습니다. 이는 Redux 애플리케이션에서 네트워크 요청의 표준 접근 방식입니다. Redux Saga는 더 복잡한 시나리오(요청 취소, 경쟁 조건, 병렬 작업, 사용자 입력 디바운스)를 위해 생성기(yield)를 사용합니다. Redux Saga (2026)에 따르면, Saga 코루틴은 중첩된 Thunk 콜백보다 테스트 및 디버그가 더 쉽습니다.
Ktor Client(JetBrains)는 파이프라인 미들웨어를 기반으로 HTTP 처리를 구축합니다. 요청의 각 단계(연결 설정, 헤더 전송, 응답 읽기)는 파이프라인의 개별 단계로 표시됩니다. 개발자는 client.install { }을 통해 플러그인(미들웨어)을 설치하여 처리 체인을 얻습니다. 설치 순서에 따라 Logging, Auth, ContentNegotiation, Caching 중 어떤 미들웨어가 먼저 데이터를 처리할지 결정됩니다.
val client = HttpClient {
install(Logging) {
level = LogLevel.BODY
}
install(Auth) {
bearer {
loadTokens { BearerTokens("access", "refresh") }
}
}
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(HttpTimeout) {
requestTimeoutMillis = 15000
}
}
설치된 미들웨어 플러그인이 있는 Ktor Client 구성. Logging — 요청 및 응답 본문 기록. Auth — 리프레시 지원과 함께 Bearer 토큰 자동 추가. ContentNegotiation — JSON 직렬화/역직렬화. HttpTimeout — 타임아웃 설정. 각 플러그인은 독립적이며, 테스트 환경에서는 요청 코드를 변경하지 않고 클라이언트 구성을 교체하여 Auth를 비활성화할 수 있습니다.
Dio는 Flutter에서 널리 사용되는 HTTP 클라이언트로, Interceptor를 미들웨어로 사용합니다. Interceptor는 전송 전에 RequestOptions를, 수신 후에 Response를 가로채며, 여러 인터셉터의 체인을 지원합니다. Dio Interceptor는 Dart/Flutter의 OkHttp Interceptor와 유사합니다. Dio (2026)에 따르면, RetryInterceptor와 LogInterceptor는 Flutter 프로젝트에서 가장 많이 사용되는 미들웨어입니다.
Bloc에는 별도 구성 요소로서의 내장 미들웨어가 없지만, 패턴은 BlocObserver(애플리케이션의 각 블록에서 이벤트를 수신하는 전역 옵저버)를 통해 구현됩니다. BlocObserver.onEvent는 각 이벤트 처리 전에, onTransition는 각 상태 전환 시, onError는 각 예외 시 호출됩니다. 이는 분석, 로깅, 크래시 리포팅 및 성능 모니터링을 위한 완전한 미들웨어입니다.
class AppBlocObserver extends BlocObserver {
@override
void onEvent(Bloc bloc, Object? event) {
Crashlytics.log("${bloc.runtimeType}: $event");
super.onEvent(bloc, event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
Analytics.log(transition.eventName());
super.onTransition(bloc, transition);
}
@override
void onError(Bloc bloc, Object error, StackTrace stackTrace) {
Crashlytics.recordError(error, stackTrace);
super.onError(bloc, error, stackTrace);
}
}
BlocOverrides.runZoned(() {
runApp(MyApp());
}, blocObserver: AppBlocObserver());
Dart의 Bloc을 위한 AppBlocObserver 예제. onEvent는 각 이벤트를 Crashlytics에 기록하고, onTransition는 이벤트를 분석에 전송하며, onError는 예외를 크래시 리포트에 기록합니다. BlocOverrides.runZoned를 통한 연결은 코드를 변경하지 않고 모든 블록에 대해 옵저버를 전역으로 만듭니다. 테스트에서 비활성화하려면 빈 옵저버를 전달하거나 BlocOverrides를 재정의하지 않으면 됩니다.
미들웨어는 효과적입니다. 많은 구성 요소에 영향을 미치는 작업(로깅, 인증, 분석, 캐싱, 성능 모니터링)에 적합합니다. 동일한 로직이 애플리케이션의 여러 부분에서 반복되는 경우(각 요청에 토큰 추가, 각 사용자 작업 기록, 각 화면 전환 분석) 미들웨어를 사용합니다. Dio (2026)에 따르면, 미들웨어를 통한 중앙 집중식 처리는 각 구성 요소에서 개별적으로 코드를 중복하는 것보다 버그를 25% 줄입니다.
가장 흔한 실수는 잘못된 미들웨어 순서로, 첫 번째 인터셉터가 두 번째가 추가하는 데이터를 기대하는 경우입니다. 두 번째로 흔한 실수는 메인 스레드에서 미들웨어의 차단 작업(파일 쓰기, 동기 HTTP 호출, 암호화)입니다. 세 번째는 예외 처리 부재로, 미들웨어가 예외를 던지면 전체 체인이 끊어져 action이 reducer에 도달하지 않거나 요청이 전송되지 않습니다. 항상 미들웨어 로직을 try-catch로 래핑하고 체인을 끊지 않고 Crashlytics 또는 Sentry에 오류를 기록하세요. 코드 리뷰 중에 정기적으로 미들웨어 체인을 확인하여 아키텍처 저하를 방지하세요.
자주 묻는 질문
Interceptor는 HTTP 통신을 위한 미들웨어의 특수 케이스입니다. 미들웨어는 더 광범위한 패턴으로, actions(Redux), 이벤트(Bloc), HTTP(Ktor) 및 모든 데이터 흐름을 처리할 수 있습니다. Interceptor는 항상 네트워크 계층에 바인딩되며 Request/Response만 처리합니다.
팩토리 메서드 또는 DI 컨테이너(Dagger, Koin, GetIt)를 사용하여 dev와 prod에 대해 다른 미들웨어 세트를 반환합니다. Redux에서는 테스트에서 빈 배열을 전달합니다. Ktor에서는 플러그인 없이 테스트 HttpClient를 사용합니다. 주요 원칙은 미들웨어가 코드에 하드코딩되지 않아야 한다는 것입니다.
네 — 미들웨어는 reducer 또는 다음 미들웨어에 전달하기 전에 action을 수정합니다. 예를 들어, Redux 미들웨어는 디스패처 코드를 변경하지 않고 각 action에 메타데이터(userId, timestamp, deviceId)를 추가할 수 있습니다. 주요 규칙은 원본 객체를 변경하지 않고 스프레드 연산자를 통해 새 객체를 생성하는 것입니다.
Ktor에서 이 용어들은 상호 교환 가능합니다 — Ktor Client 미들웨어와 플러그인은 같은 의미입니다. 각 플러그인은 HttpClientPlugin을 구현하고 client.install { }을 통해 설치됩니다. 모든 플러그인은 요청 파이프라인에 내장되어 처리 체인을 형성합니다.
BlocObserver.onError를 재정의합니다 — 모든 블록의 각 예외에서 호출되는 전역 핸들러입니다. 이는 각 블록에서 try-catch의 대안으로, 하나의 미들웨어가 오류를 중앙에서 처리하고 Crashlytics에 기록하며 사용자에게 스낵바를 표시합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.