Skip to content

Commit 2775ca4

Browse files
authored
Merge pull request #168 from KuzminVik/onboarding_typical_project
update onboarding typical project
2 parents ede3248 + 8fca810 commit 2775ca4

5 files changed

Lines changed: 442 additions & 586 deletions

File tree

learning/legacy.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
---
2+
sidebar_position: 25
3+
---
4+
5+
# Legacy подходы, которые можно встретить в старых проектах ICEROCK
6+
7+
## buildSrc
8+
9+
:::caution
10+
11+
Директория упразднена!
12+
13+
На новых проектах вместо `buildSrc` используется `build-logic`.
14+
15+
:::
16+
17+
`buildSrc` - [специальная директория Gradle](https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources). Она предназначена для реализации логики сборки, не привязанной к конкретному gradle модулю. По сути это исходники библиотеки, которая автоматически будет подгружена в gradle и все классы, объявленные в этой библиотеке, будут доступны в любом месте Gradle конфигурации (в `build.gradle.kts`).
18+
19+
В этой директории можно увидеть собственный `build.gradle.kts` и исходный код библиотеки. `build.gradle.kts` определяет, как будет собираться данная библиотека и какие зависимости ей требуются. Исходный код библиотеки в нашем проекте содержит только один объект `Deps`, содержащий константы и зависимости, необходимые нашему проекту.
20+
21+
В `build.gradle.kts` можно увидеть подключение нескольких зависимостей:
22+
23+
```kotlin
24+
dependencies {
25+
implementation("dev.icerock:mobile-multiplatform:0.9.2")
26+
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32")
27+
implementation("com.android.tools.build:gradle:4.1.3")
28+
}
29+
```
30+
31+
В `Deps.kt` объявлен объект `Deps` с перечислением версий зависимостей и самих зависимостей в виде констант, для удобного обращения к ним в gradle конфигурации.
32+
33+
Ниже приведено пояснение ко всем составляющим данного объекта:
34+
35+
```kotlin
36+
object Deps {
37+
// сначала перечисляются константами версии каждой из использующихся зависимостей,
38+
// они приватные, так как используются данные константы только внутри самого Deps
39+
private const val materialVersion = "1.2.1"
40+
// ...
41+
42+
// во вложенном объекте Android описываются версии используемых android sdk
43+
object Android {
44+
const val compileSdk = 30
45+
const val targetSdk = 30
46+
const val minSdk = 21
47+
}
48+
49+
// во вложенном объекте Plugins содержатся объявления каждого используемого нами gradle плагина,
50+
// в специальном контейнере GradlePlugin (данный класс приходит от зависимости mobile-multiplatform-gradle-plugin)
51+
// https://github.com/icerockdev/mobile-multiplatform-gradle-plugin/blob/master/src/main/kotlin/GradlePlugin.kt#L10
52+
object Plugins {
53+
// мы можем объявить gradle плагин, используя только id, в таком случае будет недоступно
54+
// подключение артефакта с данным плагином (для этого нужно указание свойства module),
55+
// такой подход используется для плагинов, которые подключаются с gradlePluginPortal либо
56+
// содержатся в зависимостях, которые указаны в buildSrc/build.gradle.kts
57+
val androidApplication = GradlePlugin(id = "com.android.application")
58+
// для плагинов, которые требуют подключения дополнительных артефактов, нужно указать свойство
59+
// module и в build.gradle.kts подключить в buildscript.dependencies
60+
val kotlinSerialization = GradlePlugin(
61+
id = "org.jetbrains.kotlin.plugin.serialization",
62+
module = "org.jetbrains.kotlin:kotlin-serialization:$kotlinxSerializationPluginVersion"
63+
)
64+
// ...
65+
}
66+
67+
// вложенный объект Libs содержит объявления каждой внешней библиотеки
68+
object Libs {
69+
// в Android содержатся библиотеки, относящиеся только к android,
70+
// они используются в android-app либо как зависимости androidMain sourceSet'а
71+
object Android {
72+
// объявление зависимости происходит в виде обычной строки
73+
const val appCompat = "androidx.appcompat:appcompat:$androidAppCompatVersion"
74+
// ...
75+
76+
// в Tests указываются зависимости для тестов в android-app либо androidTest sourceSet'а
77+
object Tests {
78+
// объявление также происходит в виде обычной строки
79+
const val espressoCore = "androidx.test.espresso:espresso-core:$espressoCoreVersion"
80+
// ...
81+
}
82+
}
83+
84+
// в MultiPlatform объекте содержатся объявления всех мультиплатформенных библиотек,
85+
// они используется в mpp-library и вложенных в нее модулях
86+
object MultiPlatform {
87+
// те зависимости, которые не предполагается использовать в Kotlin/Native export,
88+
// указываются в виде простой строки. Указывается артефакт метаданных, по которому
89+
// gradle найдет все артефакты для нужных таргетов
90+
const val kotlinSerialization =
91+
"org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinxSerializationVersion"
92+
// ...
93+
94+
// те зависимости, которые будут экспортироваться в iOS framework, объявляются в виде
95+
// MultiPlatformLibrary контейнера (из mobile-multiplatform-gradle-plugin)
96+
// https://github.com/icerockdev/mobile-multiplatform-gradle-plugin/blob/master/src/main/kotlin/MultiPlatformLibrary.kt#L10
97+
// для простоты чаще всего используется преобразование из строки с путем до артефакта
98+
// метаданных, через метод defaultMPL, с указанием в аргументах тех таргетов, которые
99+
// нужно автоматически заполнить (заполнение идет согласно именованию стандартной
100+
// публикации мультиплатформенных библиотек)
101+
val mokoResources = "dev.icerock.moko:resources:$mokoResourcesVersion"
102+
.defaultMPL(ios = true)
103+
// ...
104+
105+
// в Tests указываются зависимости для тестов в mpp-library и вложенных модулях
106+
object Tests {
107+
// зависимости указываются в виде строк
108+
const val kotlinTest = "org.jetbrains.kotlin:kotlin-test-common:$kotlinTestVersion"
109+
// ...
110+
}
111+
}
112+
113+
// в Detekt указываются специфичные для detekt плагина зависимости
114+
object Detekt {
115+
// зависимости указываются в виде строк
116+
const val detektFormatting = "io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion"
117+
}
118+
}
119+
120+
// в Modules указываются gradle модули нашего проекта
121+
object Modules {
122+
object Feature {
123+
// объявляются модули в виде контейнера MultiPlatformModule (из mobile-multiplatform-gradle-plugin)
124+
// https://github.com/icerockdev/mobile-multiplatform-gradle-plugin/blob/master/src/main/kotlin/MultiPlatformModule.kt#L10
125+
// в name указывается путь до gradle проекта, а в exported - флаг, нужно ли экспортировать
126+
// классы данного модуля в iOS framework
127+
val auth = MultiPlatformModule(
128+
name = ":mpp-library:feature:auth",
129+
exported = true
130+
)
131+
}
132+
}
133+
}
134+
```
135+
136+
Данный класс определяет константы, которые мы будем использовать в `build.gradle.kts` всех gradle модулей, что сокращает вероятность ошибки и позволяет менять версии/пути до библиотек в одном месте.
137+
138+
## Shared & Domain Factory
139+
140+
> Чтобы понять, что такое *Shared* и *Domain* Factory, нужно будет немного разобраться с подходом разделения ответственности и проброса необходимых зависимостей в проектах.
141+
142+
### Как было раньше?
143+
У нас были такие модули как *domain* и *shared*, а также фабрики *DomainFactory* и *SharedFactory*.
144+
145+
![project-inside-shared-domain](legacy/project-inside-shared-domain.png)
146+
147+
Модуль *domain* включал в себя описания доменных сущностей, описание классов для работы с сервером, логику преобразования серверных ответов в те самые доменные сущности, с которыми могло работать приложение. Также в нём содержалась и доменная фабрика *DomainFactory*, которая создавала классы для работы с сетью, репозитории, управляющие данными, и производила настройку http-клиента. А также именно расширениями к *DomainFactory* реализовывались создания всех остальных фабрик для фичей.
148+
149+
![domain-module](legacy/project-inside-domain.png)
150+
151+
Модуль *shared* содержал большое количество полезных расширений, вспомогательных методов, упрощений и прочих переиспользуемых между модулями вещей.
152+
153+
А внутри модуля *mpp-library* располагалась и *SharedFactory* (либо просто *Factory*, на разных старых проектах название может быть разным). Её предназначение было получить с натива все данные, необходимые для реализации *DomainFactory*, и, соответственно, *DomainFactory* на основе этих же данных могла реализовывать свои внутренние компоненты. Плюс *mpp-library* служила прослойкой для маппинга всех доменных сущностей в сущности фичей. Например, модель юзера могла быть и в модуле авторизации, и в модуле профилей. Но auth:User и profile:User - это были разные модели, и преобразование от доменной сущности domain:User (которую мы получали после преобразования ответа сервера) требовалось для каждой из них.
154+
155+
![shared-and-src](legacy/project-inside-shared-src.png)
156+
157+
И чтобы в фичах мы могли спокойно кидать запросы, использовать модели данных и применять вспомогательные методы из *shared*, приходилось добавлять практически во всех фичах зависимости на *shared*.
158+
159+
> По началу всё шло неплохо. Производительность не сильно страдала. Проблемы начались тогда, когда мы имеем уже объёмный проект, состоящий из 10-15 модулей с фичами. И в какой-то момент нам для одной из фичей надо в *shared* добавить небольшой код или поправить реализацию уже имеющегося. Это приводило к тому, что на iOS начинают пересобираться абсолютно все зависящие на shared модули, а разработчик, поменявший одну строчку, мог ждать сборки iOS по 10 с лишним минут.
160+
161+
> И вторая большая проблема заключалась в том, что мы вынуждены были плодить множество сущностей. На примере всё того же юзера у нас была сетевая сущность юзера, которую присылал бэк, доменная сущность юзера, в которую мы преобразовывали сетевую, а далее для фичей авторизации и профиля — ещё по одной сущности, которые относятся уже к самим фичам, а они, в свою очередь, должны преобразовываться из доменных. Самый банальный пример — если на сервере добавляют новое поле в сущности, которое нам нужно использовать, то его приходилось пробрасывать через все эти круги ада и тратить время.
162+
163+
### К чему мы пришли?
164+
165+
Из-за всего вышесказанного, от данного подхода было принято отказываться в сторону более нового - с учётом независимости фичи и проброса в неё внешних зависимостей.
166+
167+
> Если в рамках работы над проектом вам встречается модуль *domain*, *shared* или *DomainFactory*, то это проект, построенный на старом варианте архитектуры.
168+
>
169+
> Этот подход не актуален!
170+
>
171+
> В рамках поддержки существующих фичей, при невозможности изменения добавления зависимости от модуля на проброс зависимостей через интерфейсы, придётся использовать старый способ.
172+
> При создании же новых фичей, даже с учётом старой архитектуры в этом проекте, их нужно реализовывать актуальным способом.
173+
174+
**Что изменилось:**
175+
176+
- Модуль *domain* упразднён!
177+
178+
Сущность и модели у каждой фичи свои, в рамках модуля этой фичи. И они содержат достаточный набор данных для её работы. Но в случаях, когда одни и те же сущности должны использоваться между несколькими фичами, для того, чтобы избежать дублирования, такие сущности выносятся в отдельный модуль и в зависимость добавляется именно он.
179+
180+
- *DomainFactory* и SharedFactory упразднены.
181+
182+
Вместо них мы напрямую через koin получаем зависимости.
183+
184+
- Модуль *shared* упразднён.
185+
186+
Подобные общие компоненты реализовываются внутри модуля *mpp-library*.
187+
188+
- Для реализации логики работы с данными, либо с сервером используются репозитории. Каждая ViewModel описывает у себя интерфейс репозитория, покрывающий её нужды. Либо бывают случаи общего интерфейса репозитория на несколько ViewModel, но в рамках одной фичи. Реализация этого репозитория должна передаваться при создании ViewModel. Сами реализации создаются в модуле *mpp-library*, а он, как мы знаем, имеет информацию и о view-моделях (т.к. *mpp-library* знает о других модулях) и о сетевом слое. Соответственно в рамках него без проблем можно описать реализации этих интерфейсов и пробросить их в фабрику фичи, которая, в свою очередь, передаст реализацию во ViewModel. Это же помогает избежать пачки мапперов из сетевой сущности в доменную, из доменной в фичёвую.
86.8 KB
Loading
41.2 KB
Loading
103 KB
Loading

0 commit comments

Comments
 (0)