Реалізація Plugin-архітектури мобільного застосунку
Нещодавно до нас звернувся ритейлер: їх корпоративний застосунок мав підтримувати десятки модулів — складський облік, CRM, аналітику. Кожен модуль оновлювався різними командами в різний час, але монолітна архітектура вимагала щомісячних релізів усього застосунку навіть при зміні одного модуля. В результаті час виходу нового функціоналу становив 4–6 тижнів, а кожне оновлення несло ризик зламати інші модулі. Клієнт шукав спосіб ізолювати розробку і прискорити постачання.
Ми запропонували plugin-архітектуру: виділили ядро (авторизація, навігація, загальний UI) і перетворили модулі на незалежні плагіни. Тепер команди оновлюють свої плагіни без очікування загального релізу. Оцінка проекту зайняла 2 дні — ви можете отримати аналогічне рішення під ключ. Середня економія на релізах у таких проектах становить 10 000–25 000 € на рік.
Plugin-архітектура — крок далі модульної. Якщо в модульній архітектурі всі модулі відомі на етапі компіляції і збираються в єдиний бінарник, то plugin-архітектура передбачає, що частини застосунку можуть бути додані, замінені або оновлені незалежно від основного застосунку — іноді в рантаймі. Це дозволяє скоротити час виходу нових модулів у 3–5 разів порівняно з монолітним оновленням.
Plugin-архітектура виправдана для застосунків-платформ з партнерськими розширеннями, super app де міні-програми — це плагіни, корпоративних MDM-систем де компанія-клієнт додає свої модулі. Для стандартних застосунків без зовнішніх розширень вона надмірна — ускладнює розробку і налагодження.
Як це працює на Android
На Android динамічне завантаження коду — реальність через DexClassLoader. Плагін — це APK або DEX-файл, завантажений в рантаймі:
val pluginApkPath = File(context.filesDir, "plugin-v2.apk").absolutePath
val classLoader = DexClassLoader(
pluginApkPath,
context.codeCacheDir.absolutePath,
null,
context.classLoader
)
val pluginClass = classLoader.loadClass("com.plugin.FeatureImpl")
val plugin = pluginClass.getDeclaredConstructor().newInstance() as PluginContract
plugin.initialize(pluginContext)
PluginContract — інтерфейс, який плагін імплементує. Основний застосунок знає тільки про інтерфейс, не про реалізацію. Плагін завантажується з сервера, верифікується за цифровим підписом (JarVerifier або кастомна верифікація через SHA-256), кладеться в filesDir, завантажується.
Google Play Integrity API — для перевірки того, що завантажений плагін не був підмінений. IntegrityManager.requestIntegrityToken() перед завантаженням плагіна підтверджує цілісність запиту.
Обмеження: App Store (iOS) забороняє динамічне завантаження виконуваного коду — guideline 2.5.2. На iOS plugin-архітектура означає або compile-time плагіни (всі відомі при збірці, підключаються через протоколи), або інтерпретований контент (JavaScript через JavaScriptCore, Lua, WebAssembly) — це не нативний код і не порушує правила.
Чому plugin-архітектура вигідніша за моноліт?
Plugin-архітектура скорочує час виходу нових модулів у 3–5 разів порівняно з монолітним оновленням. Наприклад, у нашому кейсі з ритейлером час від запиту на новий модуль до його активації скоротився з 4 тижнів до 3 днів. Крім того, plugin-архітектура дозволяє ізолювати помилки: баг в одному плагіні не крашить весь застосунок. Окупність інвестицій у таку архітектуру становить 6–12 місяців для середнього enterprise-проекту. Економія на релізах — від 15 000 € на рік для команди з 5 розробників.
Як забезпечити безпеку плагінів?
Безпека — ключове питання при динамічному завантаженні коду. На Android ми застосовуємо:
- Верифікацію цифрового підпису кожного плагіна (SHA-256 + JarVerifier).
- Перевірку цілісності через Google Play Integrity API перед завантаженням.
- Завантаження тільки із захищеного сховища (
filesDir).
На iOS інтерпретовані плагіни (JS, WASM) виконуються в sandbox JavaScriptCore або WKWebView, що обмежує доступ до нативного API. Ми також використовуємо статичний аналіз коду плагінів перед додаванням у маркетплейс.
iOS: plugin через протоколи та JavaScriptCore
На iOS «плагін» у сенсі динамічно завантажуваного коду неможливий без джейлбрейка. Але plugin-архітектуру можна реалізувати через:
-
Protocol-based compile-time plugins. Кожен плагін — Swift Package, що реалізує PluginProtocol. Застосунок компілюється з усіма плагінами, але активує потрібні через конфіг. Плагіни ізольовані через модулі, доступ до API хоста — тільки через протокол.
-
JavaScriptCore як runtime. Плагін — JavaScript-файл, завантажений з сервера і виконуваний через JSContext. Хост реєструє нативні функції як JS-об'єкти: context["nativeAPI"] = nativeAPI as AnyObject. Швидкість виконання — прийнятна для бізнес-логіки, неприйнятна для рендерингу. Саме так працюють міні-програми в WeChat і деяких super app.
-
WebAssembly. З iOS 14+ WKWebView виконує WASM через JavaScript engine. Плагін компілюється в WASM (з C++, Rust, AssemblyScript), виконується в ізольованому середовищі. Взаємодія з нативним кодом — через WASM imports/exports.
Версіонування та сумісність плагінів
Найскладніша частина plugin-архітектури — не завантаження коду, а управління сумісністю. Застосунок v2.5 повинен запустити плагін, написаний під v2.0 API, і не впасти на плагіні v2.6, який очікує неіснуючого API.
Рішення — явне версіонування контракту. PluginContract має minHostVersion і targetHostVersion. При завантаженні плагіна хост перевіряє сумісність перед initialize(). Застарілі версії API позначаються @Deprecated і підтримуються протягом двох мажорних версій.
Кейс. Корпоративний super app для ритейлу: основний застосунок — авторизація, навігація, загальний UI. Плагіни — StockPlugin (складський облік), CRMPlugin (робота з клієнтами), AnalyticsPlugin (дашборди). Кожен плагін розробляється окремою командою, завантажується через MDM при першому запуску застосунку співробітником. Android: DexClassLoader з верифікацією підпису. iOS: compile-time плагіни через локальні SPM пакети, активація через feature flags. Оновлення плагіна — без оновлення основного застосунку в Google Play (через власний сервер дистрибуції для корпоративних пристроїв).
Порівняння compile-time та runtime plugin-архітектури
| Параметр |
Compile-time плагіни |
Runtime плагіни (Android) |
| Завантаження |
На етапі компіляції |
В рантаймі через DexClassLoader |
| Гнучкість оновлення |
Потрібен реліз застосунку |
Без оновлення основного застосунку |
| Безпека |
Висока (код в бінарнику) |
Потрібна верифікація підпису |
| Продуктивність |
Немає накладних витрат |
Оверхед на завантаження класу |
| Підтримка iOS |
Повністю (Apple дозволяє) |
Неможлива (порушує guideline) |
Що входить в роботу
- Архітектурна документація (діаграми, опис контрактів, API плагіна).
- Код ядра під iOS та Android (підтримка завантаження, версіонування, безпеки).
- Приклад плагіна (template) для початку розробки.
- CI/CD пайплайни для збірки та верифікації плагінів.
- Документація для розробників плагінів (гайд з інтеграції).
- Підтримка протягом 3 місяців після здачі.
Строки
| Тип системи |
Орієнтовні строки |
| Compile-time plugin система (iOS + Android) |
8–14 тижнів |
| Runtime plugin система (Android) + JS-плагіни (iOS) |
4–7 місяців |
| Повноцінна платформа з маркетплейсом плагінів |
8–14 місяців |
Ми реалізували plugin-архітектуру для 7 корпоративних замовників протягом 5+ років. Наші спеціалісти сертифіковані з Android та iOS (Google Associate Android Developer, Apple Certified iOS Developer). Гарантуємо сумісність плагінів протягом 2 мажорних версій застосунку.
Хочете оцінити plugin-архітектуру для вашого проекту? Замовте консультацію — ми підготуємо пропозицію під ключ за 2–3 дні. Зв'яжіться з нами, щоб обговорити ваші вимоги.
DexClassLoader API documentation
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 день незалежно від обраного паттерну.