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

Властелин модулей. Ульяновск

Властелин модулей. Ульяновск

Alexander Blinov

April 27, 2019
Tweet

More Decks by Alexander Blinov

Other Decks in Programming

Transcript

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

    “AS IS” к “TO BE” 4 Подведение итогов Содержание bit.ly/2XHDcHK Слайды
  2. Изоляция фичей Touched by His Noodly Appendage, a parody of

    Michelangelo's The Creation of Adam, is an iconic image of the Flying Spaghetti Monster[1] by Arne Niklas Jansson.
  3. Разбиение на фиче-модули 1 В фиче доступны все необходимые зависимости

    2 Легко отделить и тиражировать фичу для экспериментов 3 Наличие механизм навигации (диплинки / апплинки) 4 Низкая связность фичей Ожидания от подхода Стабильная работа системы 5
  4. Bussines vs Core Business Core •Логическая законченность •Максимальная независимость •Четкие

    внешние зависимости Решает конкретную бизнес задачу Практически не относится к бизнес логике приложения • Авторизация • Профиль • Резюме • Список вакансий • Base UI • List & Pagination • Metrics & Analytics • Network
  5. Слои модулей Feature 1 Feature 2 Feature 3 Feature 4

    App 1 App 2 Core Feature 2 Core Feature 3 Core Features Business Features App Core Feature 1
  6. External Internal API External Internal API External Internal API Взаимодействие

    через Mediator External Deps Internal implementation Feature API ?
  7. External Internal API External Internal API External Internal API Взаимодействие

    через Mediator External Deps Internal implementation Feature API APP Mediator Deps
  8. External Internal API APP Mediator External Internal API Deps interface

    PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) }
  9. External Internal API APP Mediator External Internal API Deps interface

    FeatureApi { fun getPositionFragment(): Fragment }
  10. External Internal API APP Mediator External Internal API Deps interface

    FeatureApi { fun getPositionFragment(): Fragment } interface FeatureApi { fun getPositionFragment(): Fragment //Допустимые внешние зависимости fun getSomeIntent(): SomeIntent fun getSomeInteractor(): SomeInteractor fun getSomeRepository(): SomeRepository // … }
  11. External Internal API APP Mediator External Internal API Deps class

    PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” } init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, ROOT_SCOPE) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(ROOT_SCOPE) } val api: FeatureApi = FeatureApiIml(ROOT_SCOPE) }
  12. External Internal API APP Mediator External Internal API Deps class

    PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” } init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, ROOT_SCOPE) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(ROOT_SCOPE) } val api: FeatureApi = FeatureApiIml(ROOT_SCOPE) }
  13. External Internal API APP Mediator External Internal API Deps class

    PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” } init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, ROOT_SCOPE) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } val api: FeatureApi = FeatureApiIml(ROOT_SCOPE) }
  14. PositionMediator PositionComponent API External Internal Stub API External Internal API

    APP Mediator External Internal API Deps MainSearchScreenMediator Component Holder MainSearchScreenComponent API External Stub API Internal
  15. PositionMediator PositionComponent API External Internal Stub API PositionComponent API External

    Internal External Internal API APP Mediator External Internal API Deps MainSearchScreenMediator Component Holder MainSearchScreenComponent API External Stub API Internal
  16. PositionMediator PositionComponent API External Internal Component Holder Stub API PositionComponent

    API External Internal External Internal API APP Mediator External Internal API Deps MainSearchScreenMediator Component Holder MainSearchScreenComponent API External Stub API Internal
  17. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  18. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  19. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  20. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  21. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  22. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  23. class PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder:

    SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } PositionMediator External Internal API APP Mediator External Internal API Deps Component Holder PositionComponent API External Stub API Internal apiStub SingleComponentHolder PositionComponent PositionDependencies
  24. External Internal API APP Mediator External Internal API Deps Устройство

    DI External Deps Internal implementation Feature API Common Deps
  25. Feature Position Some Else Feature AppRootScope RootPositionScope AppScope RootSomeElseScope SomeElsePositionScope

    AbcScope GhiScope JklScope DefScope Глубина скоупов зависимостей External Deps Common Deps
  26. class AutosearchFragment : AutosearchView { @InjectPresenter lateinit var presenter: AutosearchPresenter

    @ProvidePresenter fun providePresenter(): AutosearchPresenter { return AutosearchDI.openAutosearchScope().getInstance(AutosearchPresenter::class.java) } DI внутри модуля
  27. Скоупы зависимостей в модулях AutosearchDI Other App part Instal Dependencies

    Open Scope Инициализация & хранение 1 0 AutosearchFragment
  28. Скоупы зависимостей в модулях AutosearchDI AutosearchFragment Kill static Open Scope

    Переоткрытие System Force Initializer 1 Need Autosearch Deps 2 0
  29. Скоупы зависимостей в модулях AutosearchDI Other App part AutosearchFragment Kill

    static Open Scope Переоткрытие System Force Initializer 1 Need Autosearch Deps 2 Init Autosearch Deps 3 0
  30. Скоупы зависимостей в модулях AutosearchDI Other App part AutosearchFragment Kill

    static Open Scope Переоткрытие System Force Initializer 1 Need Autosearch Deps 2 Init Autosearch Deps 3 0 4 Instal Dependencies
  31. Внутри модуля Между модулями Старина Cicerone Smart Router A B

    State Machine C Навигация Через Dependencies Средства Android A B
  32. Внутри модуля Между модулями Старина Cicerone Smart Router A B

    State Machine C Навигация Через Dependencies Средства Android A B goo.gl/Tnqw2a goo.gl/tDmzEF
  33. External Internal API APP Mediator External Internal API Deps interface

    PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun onClosePositionScreen() } interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun onClosePositionScreen() } Закрытие фичи
  34. interface OnBackPressedListener { /** * @return true, если команда была

    обработана */ fun onBackPressed(): Boolean } interface OnBackPressable { fun addOnBackPressedListener(listener: OnBackPressedListener) fun removeOnBackPressedListener(listener: OnBackPressedListener) } BaseFragment : Fragment(), OnBackPressable, OnBackPressedListener{} Кнопка Back Обработка нажатия
  35. 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; //… } + Большие объекты
  36. 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; //… } Core Applicant Entity Employer Entity Common Entity + Большие объекты
  37. Предметная область 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 Итоговая картина
  38. 1 Команда 3 человека, которая увеличится до 10 2 2

    приложения на одной кодовой базе в (!) одном модуле 3 Multi-Activity подход и монолитные Activity и Fragment 4 Dagger 2 и пухлый ApplicationScope 5 Отсутствие Event Bus, наличие синглтонов 6 End2end тесты основных сценариев на голом Espresso Контекст
  39. Application Applicant code Employer code Common code Кодовая база Иерархия

    Applicant code Employer code Common code Кодовая база Applicant Employer Common Иерархия Flavors Modules Разделение проектов ШАГ №3
  40. DI фреймворки в модулях могут быть разные Замена Dagger2 на

    Toothpick Подготовка DI ШАГ №4 Замена JSR 330 DI фреймворка в монолите неделима Tothpick позволяет делать хаки при переходе
  41. 1 Выносить Core фичи легко и весело 2 Core представляет

    иерархию модулей Вынос Core модулей ШАГ №5
  42. 1 Замена транзакции фрагментов на Cicerone 2 Замена Activity на

    Activity + Fragment Делаем механизм для роутинга (Smart Router) 3 Не занимайтесь внедрением MVP / MVVM / MVI на этом шаге Заменяем Activity + Fragment на Fragment 4 Слезаем с Multi Activity ШАГ №6
  43. Feature 1 Feature 2 Feature 3 Feature 4 App 2

    Core Feature 2 Core Feature 3 Core Features Business Features App Core Feature 1 App 1 Отпочкование фичей ШАГ №7 App 1 Feature 5 Feature 6 Feature 7 Feature 8 Feature 9 Feature ∞
  44. 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) } val api: FeatureApi = FeatureApiIml(scopeName) } Переинициализация зависимостей PositionMediator Component Holder PositionComponent External Stub API API Internal
  45. 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) } val api: FeatureApi = FeatureApiIml(scopeName) } PositionMediator Component Holder PositionComponent External Stub API API Internal
  46. 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) } val api: FeatureApi = FeatureApiIml(scopeName) } PositionMediator Component Holder PositionComponent External Stub API API Internal
  47. class PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder

    { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } PositionMediator Component Holder PositionComponent External Stub API API Internal
  48. class PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder

    { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } PositionMediator Component Holder PositionComponent External Stub API API Internal
  49. class PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder

    { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } PositionMediator Component Holder PositionComponent External Stub API API Internal
  50. PositionMediator Component Holder PositionComponent External Stub API API Internal class

    PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } Initializer
  51. Feature 6 Feature 5 Feature 1 Feature 2 Feature 3

    Feature 4 App 1 App 2 Core Feature 2 Core Feature 3 Core Features Business Features App Core Feature 1 Mediator Формирование медиаторов ШАГ №8 Иерархичная связь
  52. Формирование медиаторов Отпочкование фичей Слезаем с Multi Activity Вынос Core

    модулей Подготовка DI Разделение проектов Реанимация UI тестов Анализ слабых мест №8 №7 №1 №2 №3 №4 №5 №6 Photo by Sven Mieke on Unsplash План перехода на модульную архитектуру
  53. Мысли и инсайды Создание тестовых App модулей для фичи Описание

    архитектуры в вики / статье для понимания Проведение экспериментов на Console проекте Время сборки и борьба с IDE
  54. Подход решает поставленные задачи Концепция сложная и подойдет для зрелых

    проектов Выводы Экспериментируйте. В экспериментах рождается истина
  55. Выводы Подход решает поставленные задачи Концепция сложная и подойдет для

    зрелых проектов Экспериментируйте. В экспериментах рождается истина Слайды Обзор проблем Картина “TO BE” Подведение итогов Переход от “AS IS” к “TO BE” О чем мы говорили