SlideShare a Scribd company logo
1 of 57
Download to read offline
DUMP 2017
#DUMP #REDMADROBOT
Архитектура: будь готов к
следующему миллиарду
пользователей
Блинов Александр
14/04/2017
a.blinov@redmadrobot.com
О себе
02
- Android experience 5+ year
- Android Lead REDMADROBOT
- Аспирант НГУ
План доклада
03
- Приложения в полевых условиях: теория
- Приложения в полевых условиях: практика
- Примеры
- Переходим на “Чистую архитектуру”
- Как жить с “Чистой архитектурой”?
Часть #1
Приложения в
полевых условиях: теория
Ожидание
Реальность
Мотивация
04
КАК ВЫГЛЯДИТ ТИПИЧНЫЙ ПОЛЬЗОВАТЕЛЬ?
Что требуется сделать?
05
- Поддерживать оффлайн режим работы
- Оптимизировать работу приложения для
сетей со слабым сигналом
Обычная архитектура
06
View
Presenter
Network
07
Обычная архитектура
БЛОКИРУЮЩИЙ ДИАЛОГ
Обычная архитектура
08
НЕБЛОКИРУЕМЫЙ UI
9
Архитектура с моделью
ПЕРЕСОЗДАНИЕ
Model
Архитектура с моделью
10
ТРАНСФОРМАЦИЯ
View
Presenter
Data
Network
Model
Clean Архитектура
11
ТРАНСФОРМАЦИЯ
View
Presenter
Data
Network
Repository
Business
Logic
Business
Logic
Clean Архитектура
12
ПРИМЕР С LIKE - ИНИЦИАЛИЗАЦИЯ
View
Presenter
Data
Network
Repository
Business
Logic
Clean Архитектура
13
ПРИМЕР С LIKE - ВЫСТАВЛЕНИЕ ЛАЙКА
View
Presenter
Data
Network
Repository
Clean Архитектура
14
ОТВЕТСТВЕННОСТЬ
View
Presenter
Business Logic
Repository
Services
Хранение и получение данных из
различных источников
Управление источниками данных и
предоставление их для бизнес логики
Основная логика работы приложения -
бизнес сценарии
Управление данными, получеными от пользователя
Подготовка данных от бизнес логики для отображения
Обработка пользовательского ввода, визуальное
отображение состояния
Часть #2
Приложения в
полевых условиях: практика
Стек технологий
15
- Observer (RxJava 2)
- Dependencies Injecting (Dagger 2)
Разбиение проекта
16
CLEAN ARCHITECTURE: DOMAIN
|___model //Объекты приложения
|___repository //Интерфейсы
|___interactor //Пользовательские истории
Android module
View
Presenter
Business Logic
Repository
Services
Разбиение проекта
17
CLEAN ARCHITECTURE: DATA
|___network_model //Объекты сети
|___database_model //Объекты БД
|___repository //Реализация
//Сервисы
|___network //Пользовательские истории
|___database //Пользовательские истории
|___location //Пользовательские истории
View
Presenter
Business Logic
Repository
Services
Разбиение проекта
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
Clean Архитектура
19
ТИПЫ ВЗАИМОДЕЙСТВИЕ МЕЖДУ СЛОЯМ
View
Presenter
Business Logic
Repository
Services
Прямой вызов
Все связи выстроены на
основе:
“One direction data flow”
Как следствие, имеем
только void методы и
observable
Прямой вызов
Прямой вызов
Прямой вызов
Observable
Observable
Observable
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
DI граф зависимостей
21
ВЫВОДЫ
- Observer (RxJava 2 )
- Управление потоками
- Активная модель
- Dependencies Injecting (Dagger 2)
- Иерархия зависимостей
- Неизбыточная видимость
Часть #3
Примеры
Часть #3
Примеры
Пагинация списка
22
Пагинация
ИСХОДНЫЕ УСЛОВИЯ
- Постраничная загрузка
Пагинация
23
НЕГАТИВНЫЕ СЦЕНАРИИ
- Ничего не загрузилось
- Упал один из
промежуточных запросов
- Swipe to refresh
- Появились новые данные
- Изменилось местоположение
Список элементов на сервере
Пагинация
24
ОДНОСТОРОННЯЯ ЗАГРУЗКА
Загрузка
Загруженные элементы
трансформированные в
элементы адаптера
Видимый на
экране список
Загрузка
Список элементов на сервере
Пагинация
25
Загруженные элементы
трансформированные в
элементы адаптера
Загрузка
Загрузка
Загрузка
Загрузка
Видимый на
экране список
ДВУХСТОРОННЯЯ ЗАГРУЗКА
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)
27
Пагинация
ВЫВОДЫ
- Логика загрузки в интеракторе
- Переиспользование Paginator
- Распределение ответственности между слоями
Часть #3
Примеры
Работа с картой
28
Работа с картой
ИСХОДНЫЕ УСЛОВИЯ
- Загрузка организаций на
карту
Работа с картой
29
ОСЛОЖНЯЮЩИЕ ФАКТОРЫ
- Различные изображения на
маркерах
- Маркеры создаются на
клиенте
- Изображения из сети
- Маркеров очень много
Работа с картой
30
ОСЛОЖНЯЮЩИЕ ФАКТОРЫ: ЛОГОТИПЫ
31
СТАДИИ ОБРАБОТКИ МАРКЕРОВ
Карта
Подготовка архива с
логотипами и
списком организаций
1. Распаковка архива
2. Предеплой списка организаций
3. Создание маркеров
4. Подгрузка маркеров в UI
1 2 3 4 4 4 4
SplashView
MapInteractor
MapView
MapPresenter
SplashPresenter
PrefetchInteractor
32
Карта
СХЕМА РАБОТЫ
CompanyRepo
MapImageRepo
Services
Network
Storage
Location
SplashView
MapInteractor
MapView
MapPresenter
SplashPresenter
PrefetchInteractor
33
Карта
СХЕМА РАБОТЫ
CompanyRepo
MapImageRepo
1. Распаковка
2. Предеплой списка
компаний
3. Создание маркеров
4. Подгрузка
маркеров в UI
1 2 3 4 4 4 4
Services
Network
Storage
Location
MapInteractor
MapView
MapPresenter
PrefetchInteractor
34
Карта
ОКНО ДАННЫХ
CompanyRepo
MapImageRepo
Services
Network
Storage
Location
35
Карта
ВЫВОДЫ
- Распределение нагрузки и предеплой
- Плавный UX
- Распределение ответственности между слоями
Часть #4
Переход на “Чистую архитектуру”
36
ПЕРЕХОД В РЕАЛЬНЫХ УСЛОВИЯХ
Сделаю чистую
архитектуру!
С чего начать?
- Проект не переписывают с 0
- Редизайн проходит параллельно с
поддержкой
“Чистая архитектура”
37
Шаги перехода на “Чистую архитектуру”
Шаг
Покрытие
рефакторинга
Результат
Разделение на View - Presenter По экранам Отделена логика отображения и ЖЦ
Отделение Model от Presenter По presenter Выделена модель
Заменяем синглтон App.context на
ResourceManager,PermissionManager,…
По частям в модели
В модели не происходит прямых
вызовов Android компонентов
Выносим сервисы (database, network) По частям в модели Сервисы отделены от репозиториев*
Переносим сервисы и репозиторий в DI M и P слои
Связь репозиториев, презентов и
сервисов через DI
Перегруппировка репозиториев
Репозитории*,
Презентеры DI
Репозитории* по разделам. Связь
между ними в presenter
Выносим бизнес логику в use case
Репозитории*,
Презентеры DI
DONE
Часть #5
Как жить с “Чистой Архитектурой”?
Конвенции
38
- Структура проекта
- Code style
- Стек технологий
- Основные библиотеки / фреймворки
- Kotlin
Статическая кодогенерация
39
- Android Studio Templates
- Activity + MVP
- Fragment + MVP
- Live Templates
- layout ids / text
- TAG / bundle / arguments
- …
IDE
Статическая кодогенерация
40
- App objects
- required/optional fields
- Network objects
- annotations
- fields
- Converters
- Api metods
TOOLS
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)
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)
Процессы
43
- Git flow
- Build Server (Jenkins)
- Merge conflicts
- Tests
- Build parameters
- Beta Fabric
- Jenkins integration (mail list + release notes)
CONTINUOUS INTEGRATION/ CONTINUOUS DEPLOYMENT
СI
44
BUILD SERVER BUILD PARAMETERS
CI
45
./gradlew clean

source ./version.properties



# BUILD_APK_WITH_TEST_SERVER

if [ "$BUILD_APK_WITH_TEST_SERVER" == "true" ];

then

rm -f parameters.properties



echo "application_id=com.redmadrobot.plazius" >> parameters.properties

echo "production_url=false" >> parameters.properties

echo "ssl_pinning=false" >> parameters.properties

echo "enable_logs=true" >> parameters.properties

echo "enable_crashlytics=true" >> parameters.properties



./gradlew assembleDebug

mv ./app/build/outputs/apk/app-debug.apk ./app/build/outputs/
apk/"Plazius_TestServer_$version_name-($version_code).apk"

fi
. . .
SERVER BUILD PARAMETERS
CI
46
defaultConfig {



props.load(rootProject.file('parameters.properties').newDataInputStream())

applicationId props["application_id"]



. . .



buildConfigField "Boolean", "PRODUCTION_URL", props["production_url"]

buildConfigField "Boolean", "SSL_PINNING", props["ssl_pinning"]

buildConfigField "Boolean", "ENABLE_LOGS", props["enable_logs"]

buildConfigField "Boolean", "ENABLE_CRASHLYTICS", props["enable_crashlytics"]

}

GRADLE BUILD PARAMETERS
Выводы
47
- Гибкая слоистая архитектура как религия
- Разработка приложения слоями с четко
выделенной ответственностью
- Кэширование и оффлайн режим добавляются
планомерно
- Введение общекомандного стека технологий и
практик
Ссылки
48
ЧАТ В TELEGRAM
Вопросы?
Пусть код за вас пишет робот!

More Related Content

More from it-people

«Тотальный контроль производительности» Михаил Юматов, ЦИАН
«Тотальный контроль производительности» Михаил Юматов, ЦИАН«Тотальный контроль производительности» Михаил Юматов, ЦИАН
«Тотальный контроль производительности» Михаил Юматов, ЦИАН
it-people
 
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
it-people
 
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
it-people
 
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
it-people
 
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
it-people
 
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
it-people
 
«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...
«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...
«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...
it-people
 
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
it-people
 
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...
it-people
 
"Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ...
"Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ..."Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ...
"Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ...
it-people
 
"Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже...
"Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже..."Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже...
"Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже...
it-people
 
"Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З...
"Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З..."Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З...
"Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З...
it-people
 

More from it-people (20)

«Тотальный контроль производительности» Михаил Юматов, ЦИАН
«Тотальный контроль производительности» Михаил Юматов, ЦИАН«Тотальный контроль производительности» Михаил Юматов, ЦИАН
«Тотальный контроль производительности» Михаил Юматов, ЦИАН
 
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
 
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
 
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
 
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
 
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
 
«PyWat. А хорошо ли вы знаете Python?» Александр Швец, Marilyn System
«PyWat. А хорошо ли вы знаете Python?» Александр Швец, Marilyn System«PyWat. А хорошо ли вы знаете Python?» Александр Швец, Marilyn System
«PyWat. А хорошо ли вы знаете Python?» Александр Швец, Marilyn System
 
«(Без)опасный Python», Иван Цыганов, Positive Technologies
«(Без)опасный Python», Иван Цыганов, Positive Technologies«(Без)опасный Python», Иван Цыганов, Positive Technologies
«(Без)опасный Python», Иван Цыганов, Positive Technologies
 
«Python of Things», Кирилл Борисов, Яндекс
«Python of Things», Кирилл Борисов, Яндекс«Python of Things», Кирилл Борисов, Яндекс
«Python of Things», Кирилл Борисов, Яндекс
 
«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...
«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...
«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Ra...
 
«Клиенту и серверу нужно поговорить» Прокопов Никита, Cognician
«Клиенту и серверу нужно поговорить» Прокопов Никита, Cognician«Клиенту и серверу нужно поговорить» Прокопов Никита, Cognician
«Клиенту и серверу нужно поговорить» Прокопов Никита, Cognician
 
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
 
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...
 
ПРАКТИЧЕСКИЙ ОПЫТ ИСПОЛЬЗОВАНИЯ REACT NATIVE + REDUX, Краснояров Станислав, R...
ПРАКТИЧЕСКИЙ ОПЫТ ИСПОЛЬЗОВАНИЯ REACT NATIVE + REDUX, Краснояров Станислав, R...ПРАКТИЧЕСКИЙ ОПЫТ ИСПОЛЬЗОВАНИЯ REACT NATIVE + REDUX, Краснояров Станислав, R...
ПРАКТИЧЕСКИЙ ОПЫТ ИСПОЛЬЗОВАНИЯ REACT NATIVE + REDUX, Краснояров Станислав, R...
 
"Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ...
"Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ..."Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ...
"Как повысить мотивацию удаленных разработчиков и повысить значимость каждой ...
 
"Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже...
"Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже..."Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже...
"Профессиональное выгорание менеджера проекта" Орлов Александр, Школа менедже...
 
"Как поднять цену в разы и не потерять в продажах?" Калаев Дмитрий, ФРИИ
"Как поднять цену в разы и не потерять в продажах?" Калаев Дмитрий, ФРИИ"Как поднять цену в разы и не потерять в продажах?" Калаев Дмитрий, ФРИИ
"Как поднять цену в разы и не потерять в продажах?" Калаев Дмитрий, ФРИИ
 
"Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З...
"Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З..."Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З...
"Как работать из 20 разных городов с ощущением, что находитесь в 1 комнате" З...
 
"Коммуникация в распределенной команде" Жаринов Андрей, Яндекс
"Коммуникация в распределенной команде" Жаринов Андрей, Яндекс"Коммуникация в распределенной команде" Жаринов Андрей, Яндекс
"Коммуникация в распределенной команде" Жаринов Андрей, Яндекс
 
"Не верь, не бойся, не проси" Гольдберг Игорь, СКБ Контур
"Не верь, не бойся, не проси" Гольдберг Игорь, СКБ Контур"Не верь, не бойся, не проси" Гольдберг Игорь, СКБ Контур
"Не верь, не бойся, не проси" Гольдберг Игорь, СКБ Контур
 

"Архитектура: будь готов к следующему миллиарду пользователей" Блинов Александр, REDMADROBOT