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

Lord of the modules — Nsk

8f1a6a9f39a0f45343eeff3bafa0bb15?s=47 Alexander Blinov
November 24, 2018
220

Lord of the modules — Nsk

8f1a6a9f39a0f45343eeff3bafa0bb15?s=128

Alexander Blinov

November 24, 2018
Tweet

Transcript

  1. #Devfest #hh_ru L ORD M ODULES OF THE THE

  2. Обо мне Александр Блинов Руководитель Android направления

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

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

    идея / хак Инструментарий, который использовали 4
  5. Обзор проблем и решений 5

  6. Зачем 1 Независимость фичей Разработка большой командой Переиспользование фичей на

    разных проектах нужны модули 6 Разработка большого проекта A B C E D Долгая поддержка проекта Скорость разработки на длинной дистанции Верно хотя бы одно из утверждений 2 Скорость сборки
  7. Главный вопросы 7 Как сделать иерархию модулей?

  8. Иерархичные структуры Карта скоупов 8 Резюме Отклики

  9. Циклические зависимости Резюме Отклики Каким резюме откликнулись Отправить резюме 9

    От циклических зависимостей не уйти
  10. Вакансии и резюме Резюме Отклики Каким резюме откликнулись Отправить резюме

    Толстофичи 10 Толстофичи как черные дыры
  11. Картина “TO BE” 11

  12. Что мы ждем от подхода 1 В фиче можно получить

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

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

    следующими характеристиками: - Логически законченный - Максимально независимый - Имеет четко обозначенные внешние зависимости - Практически не относится к бизнес логике приложения Пример: - Base UI - List & Pagination - Metrics & Analytics - Network 14
  15. Слои модулей 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
  16. External Deps Internal implementation Feature API Business Feature 16 Устройство

  17. Feature External Deps Internal implementation Feature API External Internal API

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

    через Mediator External Deps Internal implementation Feature API APP Mediator Deps 18 Business Feature
  19. Архтиктура готова? Нельзя так просто запилить модули!!! Архитектура готова? 19

  20. Устройство фичи Медиатор — пример работы Feature Job Position Сильная

    и независимая 20
  21. Как готовить Dependency Injection? https://goo.gl/BQHXEM https://goo.gl/9c61dz https://goo.gl/rej6kN 21

  22. Так какой DI фреймворк использовать? 22 1 Toothpick — просто

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

    API Deps
  24. Инициализация фичи 24 External Internal API APP Mediator External Internal

    API Deps interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) }
  25. Инициализация фичи 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) } }
  26. Инициализация фичи 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) } }
  27. Инициализация фичи 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) } }
  28. Инициализация фичи 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) } } Переинициализация зависимостей
  29. Инициализация фичи 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) } }
  30. Feature Position Some Else Feature Глубина скоупов зависимостей 30 AppRootScope

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

    за предел фичи Старина Cicerone Smart Router A B К примеру, “onClosePositionScreen” 31 Основные принципы https://goo.gl/Tnqw2a
  32. 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 Закрытие фичи
  33. Навигация 33 Туда и обратно interface OnBackPressedListener { /** *

    @return true, если команда была обработана */ fun onBackPressed(): Boolean } interface OnBackPressable { fun addOnBackPressedListener(listener: OnBackPressedListener) fun removeOnBackPressedListener(listener: OnBackPressedListener) } BaseFragment : Fragment(), OnBackPressable, OnBackPressedListener{}
  34. 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
  35. Applicant Entity Employer Entity Common Entity 35 Domain Entity Большие

    объекты
  36. Итоговая картина Предметная область 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
  37. Переход от “AS IS” к “TO BE” 37

  38. overflow.io 38 Сложность приложения graphviz.org Карта экранов сценариев Анализ зависимостей

    Анализ слабых мест
  39. Шаг 0. Реанимация тестов 39 Выбивание костылей должно регулярно тестироваться

    Тесты — это магия
  40. 40 Разделяем монолит

  41. Шаг 1. Отделение Applicant и Employer 41 Application Applicant code

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

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

    легко и весело 2 Core представляет иерархию модулей
  44. 44 https://goo.gl/8QwRp4 Разные Activity должны связывать только сущности, сохраняемые системой

    Шаг 4. Слезаем с Multi Activity Зачем?
  45. 45 Шаг 4. Слезаем с Multi Activity Действия 1 Замена

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

    модуля при выносе фичи 2 Выносите, что выносится 3 Размер и содержание модуля поймете опытным путем
  47. Мысли и инсайды 47

  48. Сложность в понимании Необходимо описание в Wiki Крайне тяжело отказаться

    от “прямых зависимостей” между модулями — это как перейти от наследования к композиции в первый раз 48
  49. Анализируем gradle build scan 49 Сборка модулей :transformClassesAndResourcesWithProguardForHhruDebug :transformClassesAndResourcesWithProguardForJtbDebug :transformClassesAndResourcesWithProguardForAzDebug

    Flavor-ы зло
  50. Время сборки и борьба с IDE Замена legacy и (?)

    стабильных модулей на aar compileOnly Unload modules в AS 50
  51. Ускорение разработки Создание тестовых App модулей для фичи 51

  52. Динамические фиче-модули 52

  53. Причины использования 53 Основной функционал 5 mb 40mb VR модуль

    100% 10% Частота использования Размер https://developer.android.com/guide/app-bundle/ https://developer.android.com/studio/projects/dynamic-delivery
  54. Загрузка модулей 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 }
  55. Иерархия 55 Feature 1 Feature 2 Feature 3 Feature 4

    App 1 App 2 Core Feature 1 Core Feature 2 Dynamic Delivery Feature 1 Всё пропало, шеф
  56. 56 Dynamic Delivery Feature External Internal API APP Deps Взаимодействие

    dynamic feature
  57. 57 Dynamic Delivery Feature Impl APP Deps Dynamic Delivery Feature

    Interface External API Internal Взаимодействие dynamic feature
  58. Основные компоненты Android 58 IPC всех спасёт! 1 Activity 2

    Broadcast Receiver 3 Service 4 Content Provider
  59. 59 Dynamic Delivery Feature Impl APP Deps Dynamic Delivery Feature

    Interface External API Runner Internal Provider Взаимодействие dynamic feature Runner
  60. Ограничения 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
  61. Ограничения 61 1 Moxy 2 Фреймворки, которые работают аналогичным образом

    Что еще
  62. Иерархия 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
  63. 63 Подведение итогов

  64. Разбиение на модули решает проблем больше чем создает Концепция сложная

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

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