Проблема: користувачі бачать старі баги тижнями
Припустимо, ваш e-commerce Super App містить міні-програму «Корзина». Ви виправили баг з розрахунком знижки, але поки оновлення пройде рев'ю App Store, мине 2–5 днів. Якщо баг не критичний, чекати наступного релізу додатка — ще 2–4 тижні. В результаті користувачі бачать помилку, конверсія падає на 15–20%, бізнес втрачає прибуток. Система hot-loading міні-програм вирішує цю проблему: ви публікуєте новий bundle на CDN, і через хвилини він у всіх. Але реалізація проста тільки на словах. Ми реалізували десятки таких систем — ділимося архітектурою, яка не падає. Замовте архітектуру hot-loading під ваш проект — ми допоможемо уникнути типових помилок.
Архітектура системи гарячого завантаження
Hot-loading для міні-програм — це не те саме, що Hot Module Replacement у Webpack. Це CDN-based delivery system з версійним контролем та rollback-можливістю.
Базовий flow:
- Розробник публікує нову версію міні-програми (новий bundle.zip на CDN)
- Платформа оновлює manifest — JSON з метаданими та URL нового bundle
- Super App періодично (або при запуску) перевіряє manifest-сервер
- Якщо версія змінилася — завантажує новий bundle у фоні
- При наступному відкритті міні-програми — завантажується новий bundle
Диявол у деталях кроків 3–5.
Стратегії перевірки оновлень: порівняння
| Стратегія |
Затримка доставки |
Battery impact |
Надійність на мобільних мережах |
| Polling при старті |
Години |
Низький |
Висока |
| Long polling / SSE |
Секунди |
Високий |
Низька |
| Silent push (APNs/FCM) |
Хвилини |
Середній |
Середня (залежить від iOS Doze) |
На практиці комбінуємо: silent push як основний канал + polling при запуску як fallback для пристроїв, де push не дійшов. Silent push через APNs доставляє оновлення в середньому за 5 хвилин — це в 10 разів швидше щоденного polling'у за розкладом. Polling при старті забезпечує 99.5% доставку протягом години для всіх пристроїв.
Чому атомарне оновлення критично важливе?
Не можна застосовувати bundle під час активної сесії міні-програми. Якщо замінити файли поки WebView працює — краш гарантовано.
Рішення — staged swap:
/miniapps/com.vendor.app/
current/ <- поточний active bundle (v2.3.1)
pending/ <- завантажений, але ще не застосований (v2.3.2)
rollback/ <- попередній bundle для відкочування (v2.3.0)
pending стає current тільки при наступному холодному старті міні-програми. Перейменування директорії — атомарна операція на рівні FS. Якщо під час swap стався краш хоста — pending залишиться як є, і спроба повториться при наступному запуску.
Приклад структури директорій на iOS та Android
// iOS
let baseURL = FileManager.default.applicationSupportDirectory
let appDir = baseURL.appendingPathComponent("miniapps/com.vendor.app/")
let currentDir = appDir.appendingPathComponent("current")
let pendingDir = appDir.appendingPathComponent("pending")
let rollbackDir = appDir.appendingPathComponent("rollback")
Для Android аналогічно через Context.getFilesDir().
Верифікація перед застосуванням
Перед застосуванням нового bundle — верифікація:
// iOS
let expectedHash = manifest.bundleHash // "sha256:a3f8c2..."
let actualHash = SHA256.hash(data: bundleData).hexString
guard "sha256:\(actualHash)" == expectedHash else {
throw BundleError.hashMismatch
}
Додатково: перевірка цифрового підпису маніфесту (платформа підписує manifest приватним ключем, клієнт верифікує публічним). Це захищає від атак, коли CDN підмінює bundle на шкідливий.
Якщо верифікація провалилася — bundle видаляється, продовжуємо використовувати поточну версію. В аналітику — подія з hash mismatch для моніторингу.
Як захиститися від краш-петлі?
Нова версія bundle може містити JS-помилку, яка призводить до краш-петлі. Потрібен автоматичний rollback.
Механізм: контейнер рахує consecutive crashes при завантаженні міні-програми. Якщо 3 краші підряд при старті — відкочуємося на rollback/ директорію (попередню відомо-робочу версію). Подія в аналітику, сповіщення розробнику через портал.
Крашем вважається: WKWebView навігація завершилася з помилкою, або JS викинув uncaught exception протягом 2 секунд після завантаження, або bridge не відповів на init-handshake за 5 секунд.
На стороні платформи — можливість emergency rollback: змінити currentVersion у manifest на попередню. Всі клієнти, що перевірили manifest, завантажать «старий» bundle. Це виконується за хвилини, а не години.
Диференціальні оновлення: економія трафіку в 10–30 разів
При великих bundle (> 1 MB) — диференціальні патчі замість повної заміни. Алгоритм bsdiff (Colin Percival, 2003): для оновлення v2.3.1 → v2.3.2 генерується патч-файл, який в 10–30 разів менший за повний bundle. Клієнт завантажує патч, застосовує до поточного bundle, отримує новий. Користувачі з повільним інтернетом економлять до 95% трафіку. Для проекту з аудиторією 500 тис. користувачів економія на трафіку за рахунок диференціальних патчів становить до 90%.
Вимагає зберігання бінарного bundle на клієнті (не тільки розпакованих файлів) для застосування патча. Ускладнює реалізацію, але критично важливо для користувачів з повільним інтернетом.
Типові проблеми та їх вирішення
| Проблема |
Наслідки |
Наше рішення |
| Старий bundle під час сесії |
Краш WebView |
Staged swap при холодному старті |
| Шкідливий bundle через CDN |
Витік даних |
Цифровий підпис маніфесту |
| Краш-петля нової версії |
Користувач йде |
Автоматичний rollback після 3 крашів |
| Повільний трафік |
Поганий UX |
Диференціальні патчі (bsdiff) |
Що входить у реалізацію hot-loading під ключ
- Проектування архітектури: вибір стратегії доставки, схема manifest API
- Реалізація клієнтського завантажувача (iOS/Android) з верифікацією та rollback
- Розробка CDN-агрегатора та API публікації
- Інтеграція з CI/CD: автоматична публікація bundle при мержі в main
- Документація API та схема конфігурації
- Проведення навантажувального тестування (імітація 1000+ паралельних запитів)
- Підтримка після релізу: моніторинг, алерти, hotfix через emergency rollback
Строки реалізації системи hot-loading з нуля (manifest API + CDN + client-side завантажувач з верифікацією + rollback): від 6 до 12 тижнів. З диференціальними патчами — додайте ще 3–4 тижні. Отримайте консультацію — оцінимо ваш проект і запропонуємо оптимальну архітектуру.
Наш досвід: 10+ років у мобільній розробці
Ми впровадили hot-loading у Super App чотирьох великих замовників з аудиторією від 1 млн користувачів. Жодного інциденту з втратою даних або недоступністю міні-програм після більш ніж 500 релізів. Використовуємо ті самі підходи, що описані вище. Замовте розробку — ми гарантуємо надійність і швидкість.
MVVM, Clean Architecture, BLoC, VIPER, TCA: проєктуємо архітектуру під ключ
Додаток зібрано в одному ViewController на 2000 рядків. Мережеві виклики, бізнес-логіка, оновлення UI — все в одному місці. Додати нову фічу без регресії складно, написати тест неможливо. Це не «поганий код» — це відсутність архітектури. І це трапляється частіше, ніж можна очікувати, навіть у production-додатках з мільйоном користувачів.
Ми проєктуємо архітектуру під ключ: від вибору паттерну до повної структури проєкту з тестами та документацією. За 7–10 днів отримуєте чистий модульний код, готовий до масштабування. Ваша команда зможе додавати нові фічі без ризику зламати існуючі, а тести покриють ключову логіку вже на старті. Архітектурні паттерни в мобайлі вирішують одне завдання: відокремити UI від логіки так, щоб кожна частина була тестованою та замінною.
MVVM — базовий паттерн
Model-View-ViewModel — стандарт для iOS (SwiftUI + Combine/async, UIKit + Combine) та Android (Jetpack ViewModel + StateFlow + Compose). ViewModel містить стан UI та бізнес-логіку. View лише відображає стан і передає наміри користувача до ViewModel. Model — дані та їх джерело.
Ключове правило: ViewModel не знає про UIKit або Android View-класи. Немає імпортів UIKit, немає Context-залежностей (крім Application context через Hilt). Це гарантує тестованість: ViewModel тестується як чистий Kotlin/Swift-код без Android Instrumented Test.
MVVM закриває 70% потреб. Інші 30% — де потрібна строга ізоляція фіч, масштабування команди, складний flow управління станом.
Clean Architecture — коли MVVM недостатньо
Додає шари поверх MVVM:
Domain-шар — бізнес-логіка, незалежна від платформи. UseCase (або Interactor) містить одне бізнес-правило: GetUserOrdersUseCase, PlaceOrderUseCase. Залежить лише від інтерфейсів (protocol/interface), не від конкретних реалізацій.
Data-шар — реалізація репозиторіїв. OrderRepositoryImpl реалізує OrderRepository з domain. Знає про Retrofit, Room, UserDefaults. ViewModel не знає, звідки дані — з мережі чи кешу.
Presentation-шар — ViewModel + View. Знає про Domain, не знає про Data.
Dependency rule: залежності спрямовані лише всередину. Domain не залежить ні від чого. Data та Presentation залежать від Domain.
Presentation → Domain ← Data
Це дає можливість підміняти реалізацію: тест використовує in-memory репозиторій замість мережевого, інтерфейс залишається тим самим.
Практичне зауваження: Clean Architecture додає файли та шари. Для невеликого додатку це overhead. Виправдано від ~15 фіч та при команді 3+ розробників.
BLoC для Flutter — передбачуваний потік станів
BLoC (Business Logic Component) — стандартний паттерн у Flutter-спільноті. Бібліотека flutter_bloc реалізує його через два типи: Bloc (Event → State) та Cubit (State без Events, лише методи).
Bloc обробляє Event та емітує новий State через on<EventType> хендлери. Стан імутабельний — новий об'єкт на кожну зміну. BlocBuilder перемальовує лише ту частину дерева, де змінився state.
// Подія
alias CartEvent {}
class AddItemToCart extends CartEvent {
final String productId;
AddItemToCart(this.productId);
}
// Стан
alias CartState {}
class CartLoaded extends CartState {
final List<CartItem> items;
CartLoaded(this.items);
}
// Bloc
class CartBloc extends Bloc<CartEvent, CartState> {
CartBloc(this._cartRepository) : super(CartLoaded([])) {
on<AddItemToCart>(_onAddItem);
}
Future<void> _onAddItem(AddItemToCart event, Emitter<CartState> emit) async {
final current = state as CartLoaded;
final updated = await _cartRepository.addItem(event.productId);
emit(CartLoaded(updated));
}
}
Перевага BLoC — тестованість. blocTest з bloc_test пакету дозволяє перевірити: при такому-то Event, з таким-то початковим State, BLoC має емітувати такий-то State. Без UI, без моків для Flutter-фреймворку.
VIPER — для великих iOS-проєктів
VIPER (View, Interactor, Presenter, Entity, Router) — найбільш строгий поділ обов'язків для iOS. Кожен компонент має протокол та конкретну реалізацію.
- View — лише UI, делегує все Presenter
- Interactor — бізнес-логіка, робота з мережею та даними
- Presenter — посередник між View та Interactor, форматує дані для View
- Entity — моделі даних (чисті структури)
- Router — навігація між модулями
Кожен модуль (екран або фіча) — окремий VIPER-модуль. Це виключає coupling між фічами та дозволяє великим командам працювати паралельно без конфліктів.
Ціна: багато файлів, багато протоколів. Шаблонний код генерується через Sourcery або кастомні Xcode-шаблони. VIPER виправданий для додатків з 10+ розробниками та 50+ екранами.
TCA (The Composable Architecture)
TCA від Point-Free — більш сучасна альтернатива VIPER для iOS/macOS. Основні концепції: State (імутабельний стан фічі), Action (всі можливі події), Reducer (State + Action → новий State + Effect), Store (зберігає State, обробляє Actions).
Scope дозволяє composable будувати великі фічі з маленьких: батьківський Reducer делегує частину State дочірньому. Кожна фіча тестується ізольовано через TestStore з точним контролем над Effects.
TCA має круту криву навчання, але дає передбачуваність, яку складно отримати іншим способом: кожна зміна стану — явний Action з конкретним джерелом.
Як обрати архітектуру мобільних додатків?
Оцінимо проєкт за 1 день — підберемо архітектуру з урахуванням розміру команди, платформи та планів зростання.
| Паттерн |
Платформа |
Команда |
Коли обирати |
| MVVM |
iOS, Android, Flutter |
1–5 |
Стартовий стандарт, MVP, невеликі проєкти |
| MVVM + Clean |
iOS, Android |
3–10 |
Середні проєкти, тестованість критична |
| BLoC |
Flutter |
2–8 |
Flutter з передбачуваним state management |
| VIPER |
iOS |
5–20 |
Великі iOS-проєкти, модульна архітектура |
| TCA |
iOS/macOS |
3–15 |
Сувора тестованість, Swift Concurrency |
Універсальної відповіді немає. Архітектуру обирають під розмір команди, вимоги до тестованості та горизонт підтримки додатку.
Що входить у нашу роботу
Процес складається з кількох кроків:
-
Аудит поточної архітектури (якщо додаток вже існує) — виявимо вузькі місця та регресійні зони.
-
Проєктування модульної структури з чіткими межами шарів та правилами залежностей.
-
Створення каркасу проєкту (Scaffold) з впровадженням DI, організації папок та налаштування лінтерів.
-
Написання юніт-тестів для шару домену та ViewModel — мінімум 80% покриття ключових use case.
-
Підготовка документації — архітектурні діаграми, README з правилами модифікації коду, інструкція для онбордингу нових розробників.
-
Передача робочого репозиторію з CI-пайплайном (GitHub Actions / Bitrise), налаштованим запуском тестів та статичним аналізом.
Все це входить у вартість проєктування. Додатково — підтримка на етапі впровадження: консультації команди, code review перших pull request.
Що відбувається без архітектури
Типовий сценарій через 18 місяців без архітектури: 40% часу розробки йде на дебаг регресій. Новий розробник розбирається в коді тиждень перед тим, як зробити перший PR. Тести не пишуться, «тому що складно мокувати». Додавання нової фічі вимагає розуміння половини кодової бази.
Вибір архітектури на старті — це інвестиція з поверненням через 3–6 місяців. За нашими даними, правильно спроєктована архітектура з MVVM + Clean дає в 3 рази менше регресій порівняно з монолітним ViewController. А витрати на її впровадження окупаються за 2–3 спринти. Середня економія на усуненні дефектів після впровадження — до 40% часу, що для команди з п'яти осіб може означати понад 10 000 доларів на рік.
Згідно з рекомендаціями Apple щодо проєктування додатків, розділення обов'язків — ключовий фактор стійкості коду.
Чому варто довірити архітектуру професіоналам?
Неправильний вибір паттерну на старті веде до переписування половини коду через рік. Ми бачили десятки проєктів, де спроба заощадити на архітектурі оберталася багатомісячним рефакторингом. У нас за плечима 10+ років комерційної розробки, досвід роботи з додатками від 1 до 50 розробників, понад 100 успішно реалізованих проєктів. Ми допомагаємо уникнути типових помилок:
- Overengineering для простого MVP (призначаємо MVVM, а не VIPER).
- Відсутність dependency injection — підключаємо Hilt/Koin/Dagger вже на старті.
- Ігнорування тестованості — закладаємо протоколи/інтерфейси з першого коміту.
Зв'яжіться з нами через Telegram або email — отримайте безкоштовну оцінку вашого проєкту та рекомендацію щодо оптимальної архітектури. Замовте проєктування вже сьогодні — і за тиждень стартуйте з чистим, масштабованим кодом.
Додаткова таблиця: порівняння витрат на впровадження
| Паттерн |
Час проєктування |
Кількість файлів на 1 екран |
Час написання тестів |
| MVVM |
2–3 дні |
5–7 |
1 день |
| MVVM + Clean |
4–5 днів |
10–12 |
2 дні |
| BLoC |
3–4 дні |
6–8 |
1.5 дня |
| VIPER |
5–7 днів |
12–15 |
2.5 дня |
| TCA |
5–6 днів |
8–10 |
2 дні |
Час вказано для команди з 2–3 розробників. З нашим шаблоном (generator) стартовий каркас готовий за 1 день незалежно від обраного паттерну.