Trawling

중국의 슈퍼 앱은 어떻게 동작하는가 — Flutter로 파헤쳐 본 미니 프로그램 아키텍처

WeChat, Alipay, Douyin 같은 중국 슈퍼 앱이 수만 개의 서드파티 미니 프로그램을 앱스토어 심사 없이 실행하는 기술적 구조를 분석한 글입니다. 핵심은 셸(shell) 안에 임베드된 스크립트 엔진과 JSBridge이며, 순수 Flutter로는 이 모델을 재현할 수 없다고 지적합니다. 대신 모듈형 모놀리스, SDUI, dart_eval, Flutter Web WebView 등 Flutter 생태계에서 가능한 네 가지 대안을 비교합니다.

본문

"WeChat은 WhatsApp이면서 은행이고 Uber다"라는 비유는 슈퍼 앱의 표면만 보여준다고 저자는 말합니다. 이 글은 제품 담론이 아니라 아키텍처 관점에서, 하나의 앱이 어떻게 수만 개의 서드파티 서비스를 앱스토어 심사 없이 실행하는지를 설명합니다.

슈퍼 앱의 세 계층

슈퍼 앱은 기술적으로 세 층으로 이루어집니다. 사용자가 스토어에서 내려받는 호스트/셸은 인증·결제·시스템 권한·최상위 내비게이션을 담당하고, 셸 안에 임베드된 런타임이 서드파티 코드를 격리해 실행하며, 그 위에서 미니 프로그램이 스토어 심사 없이 배포·업데이트됩니다. 핵심 부품은 이 런타임이고, 여기에 함정이 있습니다.

왜 '동적 Flutter'는 존재하지 않는가

각 미니앱을 Dart 모듈로 묶어 동적 로딩할 수 없는 이유는 기술이 아니라 스토어 정책입니다. Apple의 Guideline 2.5.2는 앱이 심사 후 기능을 추가·변경하는 코드를 내려받아 실행하는 것을 금지합니다. 역사적으로 허용된 유일한 예외는 iOS 내장 WebKit·JavaScriptCore 위에서 돌아가는 인터프리티드 스크립트뿐이며, AOT로 컴파일된 Dart 바이너리는 네이티브 코드라서 이 예외에 해당하지 않습니다. Apple이 미니 프로그램 카테고리와 공개적으로 충돌해 온 만큼 이 영역의 감독은 점점 강화되고 있다고 저자는 덧붙입니다.

실제 아키텍처: MINA, APPX, JSBridge

WeChat의 MINA 프레임워크는 렌더링 계층(WXML/WXSS)을 WebView에서, 로직 계층(JS)을 DOM이 없는 순수 JSCore 스레드에서 분리 실행합니다. Alipay의 APPX는 한층 더 나아가 렌더링 스크립트(index.js)는 WebView에서, 비즈니스 로직(index.worker.js)은 전용 V8 엔진의 Worker로 실행해 두 엔진을 병렬 초기화하고 프레임워크 JS와 개발자 JS를 별도 V8 컨텍스트로 격리합니다. Douyin은 WeChat과 거의 같은 JS Core + WebView 모델에 자체 마크업(TTML/TTSS)을 씁니다. ByteDance가 오픈소스로 공개한 Lynx는 TikTok·Douyin 같은 자사 네이티브 앱을 만드는 프레임워크이지 서드파티 미니 프로그램 런타임이 아니라는 점도 구분합니다.

공통 패턴은 명확합니다. 렌더링과 로직이 별도 스레드/컨텍스트에서 돌고, 미니 프로그램 코드는 항상 셸 내장 엔진이 런타임에 해석하며, 네이티브 바이너리로 설치되지 않습니다. 두 계층을 잇는 JSBridge는 미니 프로그램이 카메라 같은 기능을 직접 호출하지 못하게 하고, 브리지가 노출한 API를 통해 비동기로 위임하게 합니다. 덕분에 보안(노출 범위를 호스트가 통제)과 독립 업데이트(CDN에서 받는 JS 번들)라는 두 문제가 동시에 풀립니다. 한편 2019년부터 W3C MiniApps Working Group이 Alibaba·Baidu·Huawei·Xiaomi 등과 패키징·매니페스트·생명주기 표준화를 시도하고 있지만, 플랫폼 간 상호운용성은 아직 없다고 설명합니다.

Flutter로 되는 것과 안 되는 것

저자는 '슈퍼 앱'이라는 단어 뒤에 숨은 두 문제를 구분합니다. 첫째, 자기 팀이 개발·배포하는 다수의 내부 서비스를 한 앱에 담는 문제는 모듈형 모놀리스이며 Flutter가 매우 잘 해결합니다. Melos 기반 모노레포에서 각 서비스를 독립 Dart 패키지로 만들고, 공통 모듈 계약(routes·dependencies·initialize), GoRouter 연합 라우팅, named route와 이벤트 버스 기반 통신, 모듈별 상태 스코프(Bloc·Cubit·Riverpod)를 적용하는 구조가 이미 시장 표준입니다. 둘째, 서드파티가 스토어 심사 없이 미니앱을 올리는 문제는 Flutter가 네이티브로 풀지 못하며, WebView나 flutter_js(QuickJS 임베드) 같은 스크립트 엔진과 JSBridge, 카탈로그 시스템을 직접 만들어야 합니다. 이 경우 미니앱의 UI 계층은 사실상 Flutter가 아니라 WebView 안의 HTML/JS가 됩니다.

이미 존재하는 해법들

Alibaba의 FlutterBoost는 네이티브 화면과 Flutter 화면을 하나의 앱에서 공존시키는 통합 라우팅과 BoostChannel 이벤트 채널을 제공하며, Xianyu 같은 대규모 프로덕션 사례의 기반이 됩니다. 다만 이는 단일 바이너리 안의 컨테이너 모듈화이지 독립 배포되는 서드파티 미니 프로그램은 아닙니다. Shorebird는 수정된 Flutter 엔진이 Dart VM으로 코드를 해석 실행하게 해 스토어 심사 없이 패치를 배포하는 code push를 구현했는데, JS 인터프리터 예외와 같은 논리가 JavaScript에만 국한되지 않음을 보여주는 사례입니다. 다만 이는 '내 앱을 빨리 업데이트'하는 문제이지 '서드파티 코드 샌드박싱' 문제와는 신뢰 모델이 다릅니다.

100% Flutter로 가는 네 가지 경로

저자는 네 가지 접근을 비교합니다. Dart 패키지 모듈화는 법적 리스크가 없고 네이티브 성능이지만 단일 바이너리입니다. 서버가 JSON으로 화면을 기술하는 SDUI(Server-Driven UI, Google의 GenUI SDK가 2026년 알파로 공식화)는 코드가 아닌 데이터만 받으므로 가장 안전하고 가볍지만, 호스트가 미리 아는 위젯 카탈로그 안에서만 표현이 가능해 자체 비즈니스 로직을 가진 자유로운 미니앱은 불가능합니다. dart_eval·flutter_eval은 Dart 바이트코드를 앱 내에서 해석해 진짜 Flutter 위젯을 렌더링하지만 AOT 대비 10~50배 느리고 법적으로 회색지대입니다. Flutter Web을 WebView에 싣는 방식은 Guideline 2.5.2가 명시한 웹 콘텐츠 예외에 정확히 해당해 견고하지만 CanvasKit Wasm 1.5~2MB의 초기 비용과 WebView 특유의 스크롤·키보드·제스처 문제가 있습니다.

결론

내부 서비스가 많은 큰 앱을 조직화하는 것이 목표라면 패키지·계약·연합 라우팅·이벤트 버스라는 정공법이 100% Flutter로 가능합니다. 반면 중국식 서드파티 미니 프로그램 생태계를 진짜로 재현하려면 스크립트 엔진·네이티브 브리지·샌드박싱이라는 플랫폼 문제를 풀어야 하고, 이 경우 Flutter는 서비스가 도는 계층이 아니라 셸 계층으로 격하됩니다. MVP나 포트폴리오 데모라면 규정 준수를 우아하게 해결하는 SDUI가 가장 흥미로운 각도이고, 로직의 완전한 자유가 필요하면 Flutter Web in WebView가 가장 견고하다고 저자는 정리합니다.

원문: dev.to / 번역·요약: Trawling

참고 자료

오류 제보