Slide 1

Slide 1 text

МЕССЕНДЖЕР НАИЗНАНКУ Максим Соколов, iOS @ Avito
 Ноябрь 2017 / Mobius max_sokolov

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

ЧТО СЕГОДНЯ УМЕЕТ ЛЮБОЙ МЕССЕНДЖЕР? 3

Slide 4

Slide 4 text

ТЕКСТОВЫЕ СООБЩЕНИЯ НЕОТПРАВЛЕННЫЕ СООБЩЕНИЯ ЧЕРНЫЙ СПИСОК РЕАЛТАЙМ УВЕДОМЛЕНИЯ СТАТУСЫ СООБЩЕНИЙ ОНЛАЙН СТАТУСЫ ШАРИНГ ФОТО ХРАНИЛИЩЕ СООБЩЕНИЙ ШАБЛОНЫ ОТВЕТОВ 4

Slide 5

Slide 5 text

Как спроектировать все так, чтобы потом не было больно? 5

Slide 6

Slide 6 text

Спойлер Несколько приложений - один мессенджер 6

Slide 7

Slide 7 text

План Проектирование API слоя 
 взаимодействия с сервером Проектирование сервисного слоя Трюки и советы 7

Slide 8

Slide 8 text

План Проектирование API слоя 
 взаимодействия с сервером Проектирование сервисного слоя Трюки и советы 8

Slide 9

Slide 9 text

Что такое API слой? Все что умеет делать Мессенджер, но это еще не бизнес-логика. 9

Slide 10

Slide 10 text

RESPONSE SERIALIZER REQUEST SERIALIZER MESSENGER CLIENT NETWORK TRANSPORT 10

Slide 11

Slide 11 text

RESPONSE SERIALIZER REQUEST SERIALIZER MESSENGER CLIENT NETWORK TRANSPORT 11

Slide 12

Slide 12 text

Сетевой транспорт TCP Socket Web Socket HTTP (Polling / Long Polling) ? 12

Slide 13

Slide 13 text

Задача В приложении нужен чат, никакого своего бекенда пока нет. 13

Slide 14

Slide 14 text

Сетевой транспорт TCP Socket Web Socket HTTP (Polling / Long Polling) BaaS (Backend as a Service) 14

Slide 15

Slide 15 text

1 x App Середина 2015 BaaS Эволюция Мессенджера 15

Slide 16

Slide 16 text

“APIs for developers building secure realtime Mobile, Web, and IoT Applications.” 16

Slide 17

Slide 17 text

Отправить сообщение Подписаться на новые сообщения Получить историю переписки PROFIT! Backend as a Service 17

Slide 18

Slide 18 text

Не все сценарии можно реализовать 
 (статусы доставки / прочтения, черный список)* Нет контроля над перепиской 
 (пре-обработка / спам)* Минусы Backend as a Service 18

Slide 19

Slide 19 text

Забавный факт Мы использовали parse.com и он закрылся :( На BaaS надейся… 19

Slide 20

Slide 20 text

1 x App Backend в разработке 1 x App Конец 2015 HTTP Середина 2015 BaaS Эволюция Мессенджера 20

Slide 21

Slide 21 text

Задержки
 (Polling / Long polling - стратегия обновлений) Лишний трафик, накладно делать некоторые сценарии*
 (http заголовки + хендшейки на соединение*) Лишние запросы
 (новых событий может не быть) HTTP Минусы 21

Slide 22

Slide 22 text

1 x App 1 x App Конец 2015 HTTP Середина 2015 BaaS 4 x App Конец 2016 Web Socket Backend в разработке Эволюция Мессенджера 22

Slide 23

Slide 23 text

Забавный факт Пришел веб-сокет, но приложение уже написано на BaaS - мы не хотим переписывать… 23

Slide 24

Slide 24 text

Web Socket Нюансы работы с веб-сокетами 24

Slide 25

Slide 25 text

Протокол взаимодействия клиента и сервера 25

Slide 26

Slide 26 text

REQUEST-1 REQUEST-2 NOTIFICATION RESPONSE-1 RESPONSE-2 CLIENT-SERVER SOCKET CONNECTION 26

Slide 27

Slide 27 text

JSON-RPC 27

Slide 28

Slide 28 text

--> {"method": "sum", "params": [41, 1], "id": 1} <-- {"result": 42, "id": 1} 28

Slide 29

Slide 29 text

Дублирование сообщений в UI 29

Slide 30

Slide 30 text

SEND MSG NOTIF MSG-ID RESP MSG-ID DEVICE-1 SOCKET CONNECTION 30 DEVICE-2 SOCKET CONNECTION ОДИН ПОЛЬЗОВАТЕЛЬ - МНОГО СЕССИЙ NOTIF MSG-ID

Slide 31

Slide 31 text

Добавить в тело сообщения random id, 
 назначенный на клиенте Обрабатывать нотификации после того,
 как получены все ответы к запросам Решения Дублирование сообщений 31

Slide 32

Slide 32 text

Ping / Heartbeat Реконнекты Background Task Поддержка соединения 32

Slide 33

Slide 33 text

Web Socket Ack пакетов - подтверждение получения пакетов клиентом 33

Slide 34

Slide 34 text

ACKNOWLEDGED MSG-1 MSG-1 iOS ANDROID MSG-1 ACKNOWLEDGED MSG-1 MESSENGER SERVER 34

Slide 35

Slide 35 text

Web Socket & Polling? 35

Slide 36

Slide 36 text

Коды ошибок при разрыве 
 (401 / рефреш токена) Очередь на запросы к серверу 
 (assert на превышение) Обработка ошибок 36

Slide 37

Slide 37 text

Итог Выбирайте сетевой транспорт под задачу (TCP Socket / Web Socket / HTTP / BaaS) 37

Slide 38

Slide 38 text

REQUEST SERIALIZER RESPONSE SERIALIZER MESSENGER CLIENT NETWORK TRANSPORT 38

Slide 39

Slide 39 text

socket.isConnecting? 39

Slide 40

Slide 40 text

enum MessengerRequestState { case ready case delayed } 40

Slide 41

Slide 41 text

RESPONSE SERIALIZER REQUEST SERIALIZER MESSENGER CLIENT NETWORK TRANSPORT 41

Slide 42

Slide 42 text

MESSENGER API CORE Unbox Unbox Unbox In-house Mapper 42

Slide 43

Slide 43 text

Проблема Будут ли приложения определять свой контекст моделей данных? 43

Slide 44

Slide 44 text

final class MessengerResponseMapperImpl { func map( responseData: Data, completion: @escaping (_ result: Result) -> ()) { } } 44 protocol MessengerResponseMappable: class { static func map(_ dictionary: [String: String]) -> Self? } let dictionary = jsonParser.parse(data: responseData) let value = T.map(dictionary) completion(.value(value))

Slide 45

Slide 45 text

extension MessengerResponseMappable where Self: Unboxable { static func map(_ dictionary: [String: String]) -> Self? { return try? unbox(dictionary: dictionary) } } 45

Slide 46

Slide 46 text

Итог Не завязывайте маппинг ответов сетевого слоя на конкретную реализацию 46

Slide 47

Slide 47 text

MESSENGER CLIENT RESPONSE SERIALIZER REQUEST SERIALIZER NETWORK TRANSPORT 47

Slide 48

Slide 48 text

open class MessengerClient { /* Inject: Network transport Request serializer Response serializer */ func getChats( completion: @escaping (_ result: Result<[T]>) -> Void) { } } 48

Slide 49

Slide 49 text

messengerClient.getChats( completion: { (result: Result<[Chat]>) in } ) 49

Slide 50

Slide 50 text

messengerClient.getChats( completion: { (result: Result<[OtherChat]>) in } ) 50

Slide 51

Slide 51 text

MESSENGER CLIENT RESPONSE SERIALIZER REQUEST SERIALIZER NETWORK TRANSPORT MESSENGER API CORE 51

Slide 52

Slide 52 text

Выносите слой работы с API 
 в отдельный модуль
 (мы используем Private CocoaPods) Вывод 52

Slide 53

Slide 53 text

План Проектирование API слоя 
 взаимодействия с сервером Проектирование сервисного слоя Трюки и советы 53

Slide 54

Slide 54 text

Легко тестировать Легко расширять Легко переиспользовать Легко композировать сервисы друг с другом Сервисный слой 54

Slide 55

Slide 55 text

Легко тестировать Легко расширять Легко переиспользовать Легко композировать сервисы друг с другом Сервисный слой 55

Slide 56

Slide 56 text

НОВОЕ СООБЩЕНИЕ В ЧАТЕ СТАТУС СООБЩЕНИЯ ЮЗЕР ОНЛАЙН ЧЕРНЫЙ СПИСОК 56

Slide 57

Slide 57 text

МИКРОСЕРВИСНАЯ АРХИТЕКТУРА 57

Slide 58

Slide 58 text

CHAT LIST SERVICE USERS ONLINE STATUS SERVICE MESSAGES STATUS SERVICE NEW MESSAGE SERVICE BLACK LIST SERVICE DELETE CHAT SERVICE REACHABILITY SERVICE 58

Slide 59

Slide 59 text

FAVOR COMPOSITION 59

Slide 60

Slide 60 text

Как связать все вместе? 60

Slide 61

Slide 61 text

OBSERVER PATTERN Шаблон проектирования Наблюдатель 61

Slide 62

Slide 62 text

protocol UsersOnlineStatusObservable: class { func add( observer: AnyObject, onUsersOnlineStatusChange: @escaping (([User]) -> ()) ) func remove(observer: AnyObject) } 62

Slide 63

Slide 63 text

protocol UsersOnlineStatusService: class { func startPollingOnlineStatuses() } 63

Slide 64

Slide 64 text

final class UsersOnlineStatusServiceImpl: UsersOnlineStatusService, UsersOnlineStatusObservable { } 64

Slide 65

Slide 65 text

// MARK: - state private var chats: [Chat] { } func subscribeOnUpdates() { usersOnlineStatusServiceObservable.add( observer: self, onUsersOnlineStatusChange: { users in // update chats } )
 } 65 didSet { notifyObservers() } newMessagesServiceObservable.add( observer: self, onNewMessage: { newMessage in // update chats } )

Slide 66

Slide 66 text

CHAT LIST SERVICE OBSERVABLE USERS ONLINE STATUS SERVICE MESSAGES STATUS SERVICE NEW MESSAGE SERVICE BLACK LIST SERVICE DELETE CHAT SERVICE REACHABILITY SERVICE OBSERVABLE OBSERVABLE OBSERVABLE OBSERVABLE OBSERVABLE OBSERVABLE 66

Slide 67

Slide 67 text

ObserverList.swift http://gist.github.com 67

Slide 68

Slide 68 text

Микросервисы - небольшие блоки, соединяющиеся в единую систему Консистентность - все сервисы спроектированы одинаково Выводы 68

Slide 69

Slide 69 text

План Проектирование API слоя 
 взаимодействия с сервером Проектирование сервисного слоя Трюки и советы 69

Slide 70

Slide 70 text

onChatListChange -> [Chat] PRESENTER VIEW INTERACTOR CHAT LIST SERVICE … [CellData] -> setViewData 70

Slide 71

Slide 71 text

onChatListChange -> [Chat] PRESENTER VIEW INTERACTOR CHAT LIST SERVICE … [CellData] -> setViewData 71

Slide 72

Slide 72 text

Проблема Слишком частые обновления UI 72

Slide 73

Slide 73 text

DEBOUNCE THROTTLE SAMPLE 73 DELAY

Slide 74

Slide 74 text

Sample Operator Emit the most recent items emitted by an Observable within periodic time intervals 74

Slide 75

Slide 75 text

TIME TIME SAMPLE ⏱ ⏱ 75

Slide 76

Slide 76 text

let sampler = Sampler(delay: 0.5) sampler.sample { // update ui here... } 76

Slide 77

Slide 77 text

Sampler.swift http://gist.github.com 77

Slide 78

Slide 78 text

Проблема Обновление UI, если пользователь покинул экран 78

Slide 79

Slide 79 text

final class ChatListViewController: UIViewController { func processOnChatListUpdates(_ chats: [ChatViewData]) { } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) invokeClosuresHelper.setViewIsReady(true) } 79 invokeClosuresHelper.invokeOrDelayIfViewIsNotReady { // do ui updates... }

Slide 80

Slide 80 text

Проблема UITableView / UICollectionView Обновление элементов, поиск index path изменившихся 80

Slide 81

Slide 81 text

Diff алгоритм ITEM 3 ITEM 4 ITEM 2 ITEM 1 ITEM 5 ITEM 4 ITEM 6 ITEM 5 ITEM 2 ITEM 7 RELOADS INSERTS DELETES 81

Slide 82

Slide 82 text

Выбирайте инструмент под задачу Выносите логику работы c API
 в отдельный модуль Закладывайте масштабируемость Общайтесь с коллегами из других платформ Выводы 82

Slide 83

Slide 83 text

/MAXSOKOLOV @MAX_SOKOLOV https://github.com/maxsokolov https://gist.github.com/maxsokolov https://twitter.com/max_sokolov Вопросы? masokolov@avito.ru Примеры кода gist.github.com/maxsokolov