2. Архитектура: будь готов к
следующему миллиарду
пользователей
Блинов Александр
14/04/2017
a.blinov@redmadrobot.com
3. О себе
02
- Android experience 5+ year
- Android Lead REDMADROBOT
- Аспирант НГУ
4. План доклада
03
- Приложения в полевых условиях: теория
- Приложения в полевых условиях: практика
- Примеры
- Переходим на “Чистую архитектуру”
- Как жить с “Чистой архитектурой”?
16. Clean Архитектура
14
ОТВЕТСТВЕННОСТЬ
View
Presenter
Business Logic
Repository
Services
Хранение и получение данных из
различных источников
Управление источниками данных и
предоставление их для бизнес логики
Основная логика работы приложения -
бизнес сценарии
Управление данными, получеными от пользователя
Подготовка данных от бизнес логики для отображения
Обработка пользовательского ввода, визуальное
отображение состояния
19. Разбиение проекта
16
CLEAN ARCHITECTURE: DOMAIN
|___model //Объекты приложения
|___repository //Интерфейсы
|___interactor //Пользовательские истории
Android module
View
Presenter
Business Logic
Repository
Services
20. Разбиение проекта
17
CLEAN ARCHITECTURE: DATA
|___network_model //Объекты сети
|___database_model //Объекты БД
|___repository //Реализация
//Сервисы
|___network //Пользовательские истории
|___database //Пользовательские истории
|___location //Пользовательские истории
View
Presenter
Business Logic
Repository
Services
21. Разбиение проекта
18
CLEAN ARCHITECTURE: APP
|___model //UI объекты
|___presentation //Presentation слой
|___ui //Activity, Fragment, View
|___di // По фичам
|___component
|___module
|___scope
ComponentManager, DI
View
Presenter
Business Logic
Repository
Services
DI
22. Clean Архитектура
19
ТИПЫ ВЗАИМОДЕЙСТВИЕ МЕЖДУ СЛОЯМ
View
Presenter
Business Logic
Repository
Services
Прямой вызов
Все связи выстроены на
основе:
“One direction data flow”
Как следствие, имеем
только void методы и
observable
Прямой вызов
Прямой вызов
Прямой вызов
Observable
Observable
Observable
23. DI граф зависимостей
20
Device scope
User scope
User scope
Credit scope
Form 1 scope Form 2 scope
Common Repo + Common Interactors
Network Service Storage Service
Credit Repo + Credit Interactors
Form 1
Interactors
Form 2
Interactors
User Repo + User Interactors User Repo + User Interactors
24. DI граф зависимостей
21
ВЫВОДЫ
- Observer (RxJava 2 )
- Управление потоками
- Активная модель
- Dependencies Injecting (Dagger 2)
- Иерархия зависимостей
- Неизбыточная видимость
28. Пагинация
23
НЕГАТИВНЫЕ СЦЕНАРИИ
- Ничего не загрузилось
- Упал один из
промежуточных запросов
- Swipe to refresh
- Появились новые данные
- Изменилось местоположение
29. Список элементов на сервере
Пагинация
24
ОДНОСТОРОННЯЯ ЗАГРУЗКА
Загрузка
Загруженные элементы
трансформированные в
элементы адаптера
Видимый на
экране список
Загрузка
30. Список элементов на сервере
Пагинация
25
Загруженные элементы
трансформированные в
элементы адаптера
Загрузка
Загрузка
Загрузка
Загрузка
Видимый на
экране список
ДВУХСТОРОННЯЯ ЗАГРУЗКА
31. Services
Network
26
Пагинация в интеракторе
СХЕМА РАБОТЫ CompaniesView
CompaniesPresenter
CompanyRepo
LocationRepo
Storage
Location
CompaniesInteractor
Paginator
LocationInteractor
interface Paginator<Entity, ReloadConditionData> {
fun loadNextPage()
fun loadPrevPage()
fun reload(location: ReloadConditionData?)
fun observable(): Observable<PaginationState<Entity>>
fun finish()
}
interface CompaniesInteractor {
fun loadNextPage()
fun loadPrevPage()
fun reload()
fun observable(): Observable<PaginationState<Company,Location>>
fun finish()
}
class PaginationState<Entity, ReloadConditionData>(
val reloaded: Boolean,
val dataList: List<Entity>,
val reloadConditionData: ReloadConditionData,
val allLoadedStart: Boolean,
val allLoadedEnd: Boolean,
val hasLoadingErrorStart: Boolean,
val hasLoadingErrorEnd: Boolean,
val offset: Int)
35. Работа с картой
29
ОСЛОЖНЯЮЩИЕ ФАКТОРЫ
- Различные изображения на
маркерах
- Маркеры создаются на
клиенте
- Изображения из сети
- Маркеров очень много
37. 31
СТАДИИ ОБРАБОТКИ МАРКЕРОВ
Карта
Подготовка архива с
логотипами и
списком организаций
1. Распаковка архива
2. Предеплой списка организаций
3. Создание маркеров
4. Подгрузка маркеров в UI
1 2 3 4 4 4 4
43. 36
ПЕРЕХОД В РЕАЛЬНЫХ УСЛОВИЯХ
Сделаю чистую
архитектуру!
С чего начать?
- Проект не переписывают с 0
- Редизайн проходит параллельно с
поддержкой
“Чистая архитектура”
44. 37
Шаги перехода на “Чистую архитектуру”
Шаг
Покрытие
рефакторинга
Результат
Разделение на View - Presenter По экранам Отделена логика отображения и ЖЦ
Отделение Model от Presenter По presenter Выделена модель
Заменяем синглтон App.context на
ResourceManager,PermissionManager,…
По частям в модели
В модели не происходит прямых
вызовов Android компонентов
Выносим сервисы (database, network) По частям в модели Сервисы отделены от репозиториев*
Переносим сервисы и репозиторий в DI M и P слои
Связь репозиториев, презентов и
сервисов через DI
Перегруппировка репозиториев
Репозитории*,
Презентеры DI
Репозитории* по разделам. Связь
между ними в presenter
Выносим бизнес логику в use case
Репозитории*,
Презентеры DI
DONE
49. 41
Статическая кодогенерация
TOOLS: NW MODEL& APP MODEL
package com.redmadrobot.plazius.model.app
import com.redmadrobot.plazius.model.common.Guid
import java.io.Serializable
data class Order(
val orderId: Guid,
val organizationId: Guid,
val waiter: String?,
val tableNumber: Int?,
val orderItems: List<OrderItem>,
val orderSumToPay: Int,
val orderFullSum: Int,
val isRated: Boolean)
package com.redmadrobot.plazius.model.network
import com.google.gson.annotations.SerializedName
import com.redmadrobot.plazius.model.common.Guid
data class NWOrder(
@SerializedName("orderId")
val orderId: Guid? = null,
@SerializedName("organizationId")
val organizationId: Guid? = null,
@SerializedName("waiter")
val waiter: String? = null,
@SerializedName("tableNumber")
val tableNumber: Int? = null,
@SerializedName("orderItems")
val orderItems: List<NWOrderItem>? = null,
@SerializedName("orderSumToPay")
val orderSumToPay: Int? = null,
@SerializedName("orderFullSum")
val orderFullSum: Int? = null,
@SerializedName("isRated")
val isRated: Boolean? = null)
50. 42
Статическая кодогенерация
TOOLS: КОНВЕРТЕР
package com.redmadrobot.plazius.model.converter
import com.redmadrobot.plazius.exception.ConvertException
import com.redmadrobot.plazius.model.app.Order
import com.redmadrobot.plazius.model.app.OrderItem
import com.redmadrobot.plazius.model.network.NWOrder
import com.redmadrobot.plazius.model.network.NWOrderItem
import timber.log.Timber
object OrderConverter {
private val TAG = "OrderConverter"
fun fromNetwork(source: NWOrder): Order {
val orderId = getOrDie(source.orderId, "orderId")
val organizationId = getOrDie(source.organizationId, "organizationId")
val waiter = source.waiter
val tableNumber = source.tableNumber
val orderItems = source.orderItems
val orderSumToPay = getOrDie(source.orderSumToPay, "orderSumToPay")
val orderFullSum = getOrDie(source.orderFullSum, "orderFullSum")
val isRated = getOrDie(source.isRated, "isRated")
return Order(orderId,
organizationId,
waiter,
tableNumber,
orderItems?.let {
val result: MutableList<OrderItem> = mutableListOf()
if (it.isEmpty()) throw ConvertException("Order items must not be empty")
for (item in it) {
try {
result.add(OrderItemConverter.fromNetwork(item))
} catch(e: ConvertException) {
Timber.tag(TAG).e(e)
}
}
result
} ?: throw ConvertException("Order items must not be null"),
orderSumToPay,
orderFullSum,
isRated)
}
fun toNetwork(source: Order): NWOrder {
val orderId = source.orderId
val organizationId = source.organizationId
val waiter = source.waiter
val tableNumber = source.tableNumber
val orderItems = source.orderItems
val orderSumToPay = source.orderSumToPay
val orderFullSum = source.orderFullSum
val isRated = source.isRated
return NWOrder(orderId,
organizationId,
waiter,
tableNumber,
orderItems.let {
val result: MutableList<NWOrderItem> = mutableListOf()
for (item in it) {
try {
result.add(OrderItemConverter.toNetwork(item))
} catch(e: ConvertException) {
Timber.tag(TAG).e(e)
}
}
result
},
orderSumToPay,
orderFullSum,
isRated)
}
}
package com.redmadrobot.plazius.model.converter
import com.redmadrobot.plazius.exception.ConvertException
import com.redmadrobot.plazius.model.app.Order
import com.redmadrobot.plazius.model.app.OrderItem
import com.redmadrobot.plazius.model.network.NWOrder
import com.redmadrobot.plazius.model.network.NWOrderItem
import timber.log.Timber
fun fromNetwork(source: NWOrder): Order {
val orderId = getOrDie(source.orderId, "orderId")
val organizationId = getOrDie(source.organizationId, "organizationId")
val waiter = source.waiter
val tableNumber = source.tableNumber
. . .
}
fun fromNetwork(source: NWOrder): Order {
. . .
val orderItems = source.orderItems
return Order(orderId,
organizationId,
waiter,
tableNumber,
orderItems?.let {
val result: MutableList<OrderItem> = mutableListOf()
if (it.isEmpty()) throw ConvertException("Order items must not be empty")
for (item in it) {
try {
result.add(OrderItemConverter.fromNetwork(item))
} catch(e: ConvertException) {
Timber.tag(TAG).e(e)
}
}
result
} ?: throw ConvertException("Order items must not be null"),
orderSumToPay,
orderFullSum,
isRated)
55. Выводы
47
- Гибкая слоистая архитектура как религия
- Разработка приложения слоями с четко
выделенной ответственностью
- Кэширование и оффлайн режим добавляются
планомерно
- Введение общекомандного стека технологий и
практик