Неправильный путь в backend driven UI. Доклад Яндекса / Блог компании Яндекс / Хабр

Архитектура современных приложений часто позволяет передавать логику между клиентом и бэкендом. Главное — не зайти в этих экспериментах слишком далеко. Разработчик iOS-приложения Авто.ру Сергей Сергеев объяснил, как его команда пришла к backend driven UI не самым легким способом.

— Всем привет. Я iOS-разработчик, но история будет кроссплатформенная.

Всё началось с задачи. Приходит менеджер и говорит: хотим запустить отчеты про автомобили, это будет какая-то информация на одном экране. Информация разная. Мы, Авто.ру, какую-то информацию можем собрать об автомобиле, запросить что-то из баз ГИБДД. Есть еще автосервисы, на старте у нас был Fitservice.

Немаловажная часть — нужно эту информацию показывать как можно быстрее. Пользователь уже купил отчет о машине, а мы можем дополнить информацию для него.

Начинаем просто. На мобилке красим JSON, бэкенд его нам подготавливает.

Разные типы блоков с разными типами данных. Данные с Авто.ру у нас идут стрингами, мы пользователю сообщаем, что произошло. Данные из баз ГИБДД более сложные, с распечаткой, куда машина ударилась, как произошло ДТП и прочее.

Получается так. Есть key-value, можем подсветить информацию для пользователя. Дибо сделать более сложные блоки, как с ДТП, с автосервисами.

Обычная разработка мобильного приложения, пока ничего сложного.

Кроме как на бэкенде. Им пришлось сделать отдельную ручку. Есть ручка, которая просто дает данные для отчета, а есть ручка, которая подготавливает нам эти блоки. Ничего, они могут с этим работать.

В итоге мы можем менять блоки местами и добавлять блоки одного и того же типа. Можем хоть 20 автосервисов подключить. Но на кофепоинте ходят слухи, что автосервисы смогут нам отдавать распечатки по авто — какие детали они меняли или какие работы производили. Конечно, нам это тоже хочется отдавать, и тоже мгновенно. Но, понятное дело, наша модель здесь ничего сделать не сможет.

Мы думаем, как сделать красиво, а бэкенд-команда просто наблюдает, что мы делаем.

Есть обычное решение — сделать WebView. Но выглядит не очень красиво. Не то чтобы менеджеры были против, скорее со стороны разработки не хочется.

Мы не успели посмотреть какие-то кроссплатформенные фреймворки — потому что у нас уже было решение. Все давним-давно сделали у себя в приложениях сторис, далеко не только Instagram. Даже если вы продаете квартиры — у вас обязательно должны быть сторис.

В сторис можно показывать любой контент, он одинаковый для обеих платформ, потому что это новости. Верстка, конечно, должна быть серверной, потому что все сторис обновляются. У нас это сделано на интересном движке от Facebook — Yoga. Он является core-частью фреймворка ComponentKit, если под iOS, и Litho, если под Android. Также это core-часть React Native.

Как мы к нему пришли? Коротко скажу, что на iOS мы пишем не всё. Если вам интересно, как пришли, вот крутой доклад:

Скрытый текст

Если кто устал от constraints — обязательно гляньте. Там и про Android, и про iOS. В iOS мы пришли к этому во всем приложении, в Android затащили для сторис.

Рассмотрим, как это работает внутри. Код читать необязательно, тем более что тут XML, ничего интересного. XML нам дает порядок и вложенность UI-элементов, а редакторам позволяет делать их задачи удобно. В XML сразу видно, где и какие элементы есть.

Дальше нам надо понять, как эти элементы будут располагаться в своей вложенности. Здесь нам на помощь как раз приходит фреймворк Yoga. Это реализация Flexbox для мобильных девайсов. Тут тоже не буду долго останавливаться, если кто-то уже работал с Flexbox или знает, что это, — понимает, в чем тут магия: ты буквально за 20 параметров можешь расположить элемент в любой точке экрана. Для тех, кто не знает, оставлю ссылку на сайт Yoga layout, там есть отличное демо. Можно потыкать и особо ничего не читать.

Когда мы научились располагать элементы в каком-то порядке и в любом положении, нам осталось только затянуть отображение. Но в отображение мы передаем те элементы, которые нужно отобразить в приложении, здесь все ограничивается вашей любимой библиотекой. У нас это UIKit.

XML дает нам свободу, любую вложенность, любое расположение. На отображении — параметры от UIKit. Есть хитрое наследование тегов. Это тег stack, который как раз использует вложенность, и по сути это view. В iOS — UIView, в Android — своя корневая View, у нее есть набор компонентов, позволяющих что-то разукрасить, добавить закругления. Все новые view от нее наследуются.

Предположим, нам нужен текст. Заводим тег text, он наследует все параметры от stack плюс, возможно, добавляет свои по тексту и по цвету — то, что вам нужно.

Так мы делаем действительно гибкий UI. Можем затащить техосмотры, это новая информация, которой у пользователей сразу после релиза не было, а мы можем с сервера прислать этот новый блок и информацию из него. Можем компоновать информацию. Например, мы посмотрели, какой владелец в какое примерно время у этой машины был. В то же время у нее был техосмотр. Можем скомпоновать все это в отдельном блоке и показать на девайсе.

Либо можем подсветить важную информацию, например, про налоги. Здесь все хорошо, статика.

Но что с взаимодействием? Мы же отображаем старые объявления с Авто.ру. Я уже раскрыл подробности: у нас все приложение покрыто диплинками, и здесь они спасают нас примерно в 100% случаев. Например, нам нужно открыть экран объявления. Прокидываем туда ссылку, по ней открывается экран, пытаемся запарсить его, как диплинк. Если не умеем — открываем в WebView, умеем — открываем новый экран. Пользователь чувствует, что это натив, и это он и есть.

Дальше мы не только кликаем. Есть еще и контент, пользователю нужно обеспечить взаимодействие с этим контентом. У нас есть много фотографий машины, нужно поместить их в карусель. Здесь мы делаем workaround в том же XML — заводим под карусель тег scrollableLayout. Все child этого тега наследуют всё от того же View и становятся в CollectionView, то есть в карусель.

Также мы затащили «расхлопы». В XML сразу приходит верстка для схлопнутого и расхлопнутого блока, а приложение переключает эти верстки.

Примерно то же самое и с pop-up. Чтобы не выливать на пользователя много информации сразу, прячем ее в pop-up. Верстка pop-up приходит в XML, по кнопке приложение понимает, когда эту верстку отобразить.

Что мы совсем не можем этой крутой версткой сделать — красивые графики. Менеджеры, конечно, хотят, но такого не сделаешь. Он кликабельный, можно посмотреть историю пробегов у машины, но в XML не заверстаешь. Приходится на месте, где должен быть график, ставить тег: здесь расположен график. Приложение поймет, какую view использовать. То есть уже не содержится информации о том, какая view нужна.

Есть минусы: XML, который я показывал в старой версии, поддерживается, пользователю не обязательно обновляться, а вот поддерживать красивые кастомные view будут только новые версии приложений. Но это пограничный кейс, графиков у нас не так много.

Катить XML действительно круто, все отображение приходит с сервера. Пример с блоком расчета стоимости — это реальная задача, она пришла с готовым бэкендом, с дизайнами, от постановки задачи до появления блока на экране пользователя действительно прошла неделя. Три дня заняла разработка — верстка на XML, тестирование и выкладка XML, который приходит на девайсы.

Работает как магия — ровно до того момента, пока мы не начинаем задаваться вопросом, откуда брать весь этот XML. Он же динамический. Нужно, чтобы его кто-то генерировал. Команда бэкенда на нас смотрит и говорит — это что же, нам его нужно присылать?

Подождите, мы можем сделать это, как делают фронтендеры. А они делают статичные сайты на HTML, используют шаблонизаторы. Можно сделать шаблон, плейсхолдеры, забить его JSON, и на выходе получится нормальная верстка. Но нет, мы исследовали и нам это не подходит: в верстке у нас содержится бизнес-логика, то есть расположение, очередность, цвета элементов — в зависимости от того, что происходит в отчете. Предположим, пользователь указал пять владельцев, а было 10. Мы все это подсвечиваем на девайсе, то есть в верстке. Возможно, это не очень правильно, но нам с этим удобно работать. Получается, что шаблонизаторы тут совсем не подходят.

Задача должна куда-то двигаться. Мы уже показали наше крутое решение менеджерам, они уже хотят видеть это в проде, а мы так и не решили проблемы с XML. В итоге принимаем решение, что нам его будет отдавать бэкенд.

В этом как будто бы ничего плохого нет. Мобильная разработка умеет верстать под XML, несет его в бэкенд, а бэкенд делает точно так же и присылает нам в мобилку.

Это реальный комментарий и задача. Понятно, что мобильная разработка есть на iOS и Android, два разработчика верстают, третий разработчик на бэкенде эту XML программирует, как написано, и несем в QA тестировать. Мем примерно отражает ту эпоху.

Наш разработчик пишет на Scala. Это не то чтобы плохой язык для верстки XML. Проблема тут даже не в языке.

Проблема в бэкенд-разработчике. Мы ему говорим — здесь приходит что-то неправильное.

Конечно, он здесь ничего не видит. Какие еще отступы? Процесс получается так себе. Любые изменения в UI затрагивают и нас, потому что мы знаем, как это должно выглядеть, и бэкенд-разработку, потому что она это нам отдает, и дизайнера, который при виде нашего кривого UI идет к нам ругаться. Все это отдельно верстается под Android, потому что логика в итоге разошлась.

Как выглядит интерфейс? XML становится только больше, потому что подключаются новые блоки и сервисы.

В итоге на мобильных мы радуемся, но сервер в лице одного старшего Scala-девелопера, к сожалению, страдает.

Перейдем к третьей части, в которой, я надеюсь, все перестанут верстать XML. Нам самим надо научиться генерировать XML, потому что мы знаем, как он выглядит. Но генератор должен быть динамический, мы не можем зашить его в приложение — как мы тогда будем его изменять?

Так как падать нам дальше некуда, мы можем генерировать XML JS. Это удобно, в нем можно описать достаточно сложную логику, а JS мы можем гонять еще и прямо на девайсе. В iOS это делается буквально в одну строчку. Ты берешь WKWebView, говоришь: .evaluateJavaScript, кидаешь туда JS, и происходит магия. В Android немного сложнее, но все же работать оно будет.

Получается, что UI занимаемся мы, JS лежит в репозитории, мы его туда пушим, редактируем, тестируем то, что получилось, и катим в прод.

Больше не задействуем серверную разработку, она больше не занимается UI, тем более что у нее осталась только одна ручка, которая отдает данные из отчета и всё. Они совсем к нам отношения не имеют, и кажется, что уже и не хотят.

Мы научились работать с JS. JavaScript-код интересно писать после строго типизированных языков. Но дальше тысячной строчки мы не смогли без типов, поэтому завезли себе TypeScript и структурировали код. Каждый блок — это класс на TypeScript. Если нужно поправить какой-то блок — ищешь его прямо в TypeScript и правишь, все достаточно легко. Процесс стал такой, как и мы задумывали в самом начале. Менеджер приходит к нам, мы правим, нам прилетают данные из бэка, и всей версткой тоже занимаемся мы. При этом всю верстку мы присылаем с сервера.

Казалось бы, все хорошо. Но начались проблемы. Решение гонять JS на мобилках было все же спонтанным. Поначалу был какой-то процент ошибок, на который мы не обращали внимания, но потом этот процент перерос в большое количество людей, отчетов стало больше, покупать их стали больше. На iOS есть особенность: WKWebView запускается в своем процессе, отдельном от процесса самого приложения. Это, с одной стороны, интересно: ресурсы, которые выделяются на аппприложение, не выедаются. А с другой — не очень интересно, потому что за этим процессом невозможно следить, он может вернуть непонятные ошибки, процент которых у нас и был. На Android все было совсем плохо: туда затащили старую библиотеку Rhino, она поддерживала не самый новый JS и долго стартовала. На Android тоже жаловались, там было на что жаловаться.

Это уже часть 3.1, где все работает, но хочется чуть лучше. Напрашивается нормальное решение — перенести генерацию XML с девайсов, перестать делать это мощностями пользователей. Мы вызываем команду бэкенда на встречу.

Android-разработчик держит дверь, а я объясняю, что вы снова будете присылать нам верстку, но заниматься ей мы будем сами. Нам нужно, чтобы вы всё настроили, подняли у себя штуку, которая будет делать JS, графы и всё, что вы любите. А нам только JS прогнать.

На этом этапе мы сейчас и находимся. Хеппи-энд? Пока непонятно. Сейчас оно работает так, как мы и задумывали изначально, нам комфортно катить все изменения. На TypeScript писать тоже комфортно и даже интересно.

Из этого надо сделать выводы. Если какие-то серьезные, то, залезая в backend-driven UI, нужно либо сразу вводить дизайн-систему, либо смириться с тем, что дизайн на двух платформах разойдется. Нельзя просто сказать: «У нас вроде одинаковая библиотека, и все должно работать так же». Android-разработчики думают по-другому, как и я с их точки зрения.

Если делать шуточные выводы, то XML можно писать до 500-й строчки, дальше уже путаешься. JS без типов можно писать где-то до тысячной, дальше теряешь контекст и становится сложно. И еще бэкенд-разработчики точно не умеют в верстку, не стоит давать им верстать, по крайней мере Scala-разработчикам. Это всё, спасибо.

Source link

Добавить комментарий

Ваш адрес email не будет опубликован.