Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lord of the modules — Nsk

Alexander Blinov
November 24, 2018
260

Lord of the modules — Nsk

Alexander Blinov

November 24, 2018
Tweet

More Decks by Alexander Blinov

Transcript

  1. 1 Обзор проблем 2 Картина “TO BE” 3 Переход от

    “AS IS” к “TO BE” 4 Динамические фиче-модули 5 Подведение итогов Слайды goo.gl/f3Xf3z Содержание 3
  2. Условные обозначения Грабли, на которые мы наступили Умные мысль /

    идея / хак Инструментарий, который использовали 4
  3. Зачем 1 Независимость фичей Разработка большой командой Переиспользование фичей на

    разных проектах нужны модули 6 Разработка большого проекта A B C E D Долгая поддержка проекта Скорость разработки на длинной дистанции Верно хотя бы одно из утверждений 2 Скорость сборки
  4. Что мы ждем от подхода 1 В фиче можно получить

    все необходимые зависимости 2 Код фичи легко отделяем 3 Код фичи тиражируем (эксперименты) 4 Возможность управлять временем жизни фичей (скоупами) 5 Возможность навигации, в т.ч. диплинки / апплинки 12
  5. Что такое Business Feature Business Feature — программный модуль со

    следующими характеристиками: - Логически законченный - Максимально независимый - Имеет четко обозначенные внешние зависимости - Решает конкретную бизнес задачу Пример: - Авторизация - Профиль - Резюме 13
  6. Что такое Core Feature Core Feature — программный модуль со

    следующими характеристиками: - Логически законченный - Максимально независимый - Имеет четко обозначенные внешние зависимости - Практически не относится к бизнес логике приложения Пример: - Base UI - List & Pagination - Metrics & Analytics - Network 14
  7. Слои модулей Feature 1 Feature 2 Feature 3 Feature 4

    App 1 App 2 Core Feature 2 Core Feature 3 Core Features Business Features App 15 Core Feature 1
  8. Feature External Deps Internal implementation Feature API External Internal API

    External Internal API External Internal API Иллюстрация проблемы зависимостей 17 Business Feature
  9. External Internal API External Internal API External Internal API Взаимодействие

    через Mediator External Deps Internal implementation Feature API APP Mediator Deps 18 Business Feature
  10. Так какой DI фреймворк использовать? 22 1 Toothpick — просто

    в реализации 2 Dagger 2 — сложно, но можно 3 Любой другой? — расскажите потом об опыте
  11. Инициализация фичи 24 External Internal API APP Mediator External Internal

    API Deps interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) }
  12. Инициализация фичи 25 External Internal API APP Mediator External Internal

    API Deps class PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” private val counter = AtomicInteger() } private val scopeName = ROOT_SCOPE + counter.incrementAndGet() init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, scopeName) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } fun getPositionFragment(): Fragment { return PositionFragment.newInstance(activity, scopeName) } }
  13. Инициализация фичи 26 External Internal API APP Mediator External Internal

    API Deps class PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” private val counter = AtomicInteger() } private val scopeName = ROOT_SCOPE + counter.incrementAndGet() init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, scopeName) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } fun getPositionFragment(): Fragment { return PositionFragment.newInstance(activity, scopeName) } }
  14. Инициализация фичи 27 External Internal API APP Mediator External Internal

    API Deps class PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” private val counter = AtomicInteger() } private val scopeName = ROOT_SCOPE + counter.incrementAndGet() init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, scopeName) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } fun getPositionFragment(): Fragment { return PositionFragment.newInstance(activity, scopeName) } }
  15. Инициализация фичи 28 External Internal API APP Mediator External Internal

    API Deps class PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val SCOPE_NAME = “${BuildConfig.APPLICATION_ID}_POSITION_DEPS” private val counter = AtomicInteger() } private val scopeName = SCOPE_NAME + counter.incrementAndGet() init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, scopeName) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } fun getPositionFragment(): Fragment { return PositionFragment.newInstance(activity, scopeName) } } Переинициализация зависимостей
  16. Инициализация фичи 29 External Internal API APP Mediator External Internal

    API Deps internal object PositionDI { private const val POSITION_SCOPE = “${BuildConfig.APPLICATION_ID}_PositionScope_" fun openEditPositionScope(parentScopeName: String): Scope { return Toothpick.openScopes(parentScopeName, ENTER_POSITION_SCOPE + parentScopeName) .apply { installModules(EnterPositionModule()) } } fun closeEditPositionScope(parentScopeName: String) { Toothpick.closeScope(ENTER_POSITION_SCOPE + parentScopeName) } }
  17. Feature Position Some Else Feature Глубина скоупов зависимостей 30 AppRootScope

    PositionDepsScope AppScope SomeElseScope PositionScope AbcScope GhiScope JklScope DefScope
  18. Навигация 1 Навигация в фичи и App модулях 2 Навигация

    за предел фичи Старина Cicerone Smart Router A B К примеру, “onClosePositionScreen” 31 Основные принципы https://goo.gl/Tnqw2a
  19. interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun

    onClosePositionScreen() } interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun onClosePositionScreen() } 32 Навигация External Internal API APP Mediator External Internal API Deps Закрытие фичи
  20. Навигация 33 Туда и обратно interface OnBackPressedListener { /** *

    @return true, если команда была обработана */ fun onBackPressed(): Boolean } interface OnBackPressable { fun addOnBackPressedListener(listener: OnBackPressedListener) fun removeOnBackPressedListener(listener: OnBackPressedListener) } BaseFragment : Fragment(), OnBackPressable, OnBackPressedListener{}
  21. Domain Entity public class Resume { private Collection<SpecializationDto> specializations; private

    ProfessionalExperienceDto professionalExperience; private Set<ResumeCertificateDto> certificates; private AdditionalEducationDto additionalEducation; private BusinessTripReadiness businessTripsReadiness; private ContactInformationDto contactInformation; private RecommendationInfoDto recommendationInfo; private List<UserImageDto> userPortfolio; private EducationHistoryDto educationHistory; private RelocationInfoDto relocationInfo; private ResumeLanguageDto languages; private WorkExperience workExperience; private Set<Employment> employments; private EducationLevel education; private LocalDateTime creationTime; private LocalDateTime lastChangeTime; private Set<Schedule> schedules; private RoadTimeType roadTime; private ResumeStatus resumeStatus; private UserImageDto userImage; private AreaDto area; private AccessDto access; private LocalDate birthdayDate; private AreaDto[] citizenship; private AreaDto[] workTickets; private MoneyDto desirableCompensation; private MetroDto nearestMetro; private Gender gender; private String[] keySkills; private String hash; private String title; private String aboutMe; private int id; private int siteId; private boolean hasVehicle; private String[] driverLicense; //… } Большие объекты 34
  22. Итоговая картина Предметная область Applicant Entity Employer Entity Common Entity

    Общий функционал Фиче-модули User Job Position Push Suggestion University … Base UI List Processing Pagination Network Remote Config Custom Views View#1 View#2 View#3 … … Приложения Applicant Application Russia Azerbaijan Belarus Employer Application Russia Belarus 36
  23. Шаг 1. Отделение Applicant и Employer 41 Application Applicant code

    Employer code Common code Кодовая база Иерархия Applicant code Employer code Common code Кодовая база Applicant Employer Common Иерархия Flavors Modules
  24. Шаг 2. Dagger2 на Toothpick Dagger и гибкость 42 DI

    фреймворки можно комбинировать Временная замена
  25. Шаг 3. Вынос Core модулей 43 1 Выносить Core фичи

    легко и весело 2 Core представляет иерархию модулей
  26. 45 Шаг 4. Слезаем с Multi Activity Действия 1 Замена

    транзакции фрагментов на Cicerone 2 Замена Activity на Activity + Fragment Делаем механизм для роутинга (Smart Router) 3 Не занимайтесь внедрением MVP / MVVM / MVI на этом шаге
  27. Шаг 5. Отпочкование фичей 46 Выводы 1 Минимизируйте переписывание Application

    модуля при выносе фичи 2 Выносите, что выносится 3 Размер и содержание модуля поймете опытным путем
  28. Сложность в понимании Необходимо описание в Wiki Крайне тяжело отказаться

    от “прямых зависимостей” между модулями — это как перейти от наследования к композиции в первый раз 48
  29. Время сборки и борьба с IDE Замена legacy и (?)

    стабильных модулей на aar compileOnly Unload modules в AS 50
  30. Причины использования 53 Основной функционал 5 mb 40mb VR модуль

    100% 10% Частота использования Размер https://developer.android.com/guide/app-bundle/ https://developer.android.com/studio/projects/dynamic-delivery
  31. Загрузка модулей 54 class SplitFeatureProcessor(private val context: Context) { private

    val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(context).also { it.registerListener { status -> doSomething(status) } } fun installModule() { val request = SplitInstallRequest .newBuilder() .addModule(“dynamic_feature_vr”) .build(); splitInstallManager .startInstall(request) .addOnSuccessListener { sessionId -> Toast.makeText(context, "Success: $sessionId", Toast.LENGTH_SHORT).show() } .addOnFailureListener { exception -> Toast.makeText(context, "Fail: ${exception.message}", Toast.LENGTH_SHORT).show() } } //Some code }
  32. Иерархия 55 Feature 1 Feature 2 Feature 3 Feature 4

    App 1 App 2 Core Feature 1 Core Feature 2 Dynamic Delivery Feature 1 Всё пропало, шеф
  33. 57 Dynamic Delivery Feature Impl APP Deps Dynamic Delivery Feature

    Interface External API Internal Взаимодействие dynamic feature
  34. 59 Dynamic Delivery Feature Impl APP Deps Dynamic Delivery Feature

    Interface External API Runner Internal Provider Взаимодействие dynamic feature Runner
  35. Ограничения 60 https://developer.android.com/guide/app-bundle/#known_issues 1 Не поддерживает “APK expansion files” и

    APK > 100 mb 2 Конфликтует с инструментами меняющими таблицу ресурсов 3 В Manifest-е динамического фичи модуля только ресурсы базового модуля 4 Для работы необходимы Android 5.0 (API level 21) и последняя версия Google Play Store 5 Прочие баги, ограничения и недоработки На сайте Android Developers
  36. Иерархия 62 Feature 1 Feature 2 Feature 3 Feature 4

    App 1 App 2 Core Feature 1 Core Feature 2 Dynamic Delivery Feature 1 Dynamic Delivery Feature Runner 1 50± kb 40mb
  37. Разбиение на модули решает проблем больше чем создает Концепция сложная

    и подойдет для зрелых проектов Не бойтесь экспериментировать и никому не верьте! Выводы 64
  38. О чем мы говорили? Выводы Разбиение на модули решает проблем

    больше чем создает Концепция сложная и подойдет для зрелых проектов Не бойтесь экспериментировать и никому не верьте! 65 Обзор проблем Картина “TO BE” Подведение итогов Слайды t.me/xanderblinov Контакты Переход от “AS IS” к “TO BE” Динамические фиче- модули