mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-05-25 22:08:06 +02:00
UI started
This commit is contained in:
parent
895a38f86b
commit
885d89cddb
@ -207,4 +207,6 @@
|
||||
|
||||
Как мы видим, простая операция «попробовать продлить предложение» выливается в громоздкий код, в котором легко ошибиться, и, что ещё важнее, который совершенно не нужен разработчику приложения. Было бы гораздо проще, если бы этой ошибки *вовсе не было в SDK*, т.е. попытки обновления и перезапросы выполнялись бы автоматически.
|
||||
|
||||
Аналогичные ситуации возникают и в случае нестрого-консистентных API или оптимистичного управления параллелизмом — и вообще в любом API, в котором фон ошибок является ожидаемым (что в в случае распределённых клиент-серверных API является нормой жизни). Передача версии ресурса и/или последних известных идентификаторов в эндпойнты с политикой read-your-writes — техническая необходимость, вызванная стремлением вендора API удешевить эксплуатацию и увеличить пропускную способность. Для разработчика приложения написание кода, имплементирующего эти политики — попросту напрасная трата времени.
|
||||
Аналогичные ситуации возникают и в случае нестрого-консистентных API или оптимистичного управления параллелизмом — и вообще в любом API, в котором фон ошибок является ожидаемым (что в в случае распределённых клиент-серверных API является нормой жизни). Передача версии ресурса и/или последних известных идентификаторов в эндпойнты с политикой read-your-writes — техническая необходимость, вызванная стремлением вендора API удешевить эксплуатацию и увеличить пропускную способность. Для разработчика приложения написание кода, имплементирующего эти политики — попросту напрасная трата времени.
|
||||
|
||||
5. Хранение данных в постоянной памяти (таких, как токены авторизации, ключи идемпотентности при перезапросах, идентификаторы черновиков при двухфазных коммитах и т.д.) также является ответственностью клиента и с трудом поддаётся формализации при кодогенерации.
|
||||
|
@ -1,3 +1,58 @@
|
||||
### UI-библиотеки
|
||||
|
||||
#### Особенности предметной области
|
||||
Введение в состав SDK UI компонентов обогащает и так не самую простую конструкцию из клиент-серверного API и клиентской библиотеки дополнительным измерением: теперь с вашим API взаимодействуют одновременно и разработчики (которые написали код приложения), и пользователи (которые непосредственно тыкают пальцами в экран). Хотя это изменение на первый взгляд может показаться не очень значительным, с точки зрения дизайна API добавление конечного пользователя — огромная проблема, которая требует на порядок более глубокой и качественной проработки дизайна программных интерфейсов по сравнению с «чистым» клиент-серверным API. Попробуем объяснить, почему так происходит, на конкретном примере.
|
||||
|
||||
Пусть мы решили поставлять в составе нашего кофейного API так же и клиентский SDK, который предоставляет готовые компоненты для разработчиков приложений. Достаточно простая функциональность: пользователь вводит поисковый запрос и видит результаты в виде списка либо в виде точек на карте.
|
||||
|
||||
(здесь будет картинка)
|
||||
|
||||
Пользователь может выбрать какой-либо из объектов, и тогда появится панель действий с выбранным предложением.
|
||||
|
||||
(здесь будет картинка)
|
||||
|
||||
Для реализации этого сценария мы предоставим объектно-ориентированный API в виде, ну скажем, класса `SearchBox`, который реализует описанную функциональность поверх клиент-серверного метода `search` нашего API.
|
||||
|
||||
#### Проблемы
|
||||
|
||||
С одной стороны нам может показаться, что наш `SearchBox.search` и чистый клиент-серверный `search` — в сущности одно и то же. Увы, это не так; перечислим проблемы, с которыми мы никогда не сталкивались при разработке API без визуальных компонент.
|
||||
|
||||
##### Захват разделяемого ресурса
|
||||
|
||||
Предположим, что мы хотим разрешить разработчику подставить в наш `SearchBox` свой поисковый запрос. Допустим, потому что где-то в приложении размещён рекламный баннер «найти лунго рядом со мной», по нажатию на который происходит переход к нашему компоненту с введённым запросом «лунго». Этот переход мы выполним, допустим, разработав метод `SeachBox.search`.
|
||||
|
||||
Теперь у нас два метода `search`, которые принимают одни и те же параметры и выдают один и тот же результат. Но *ведут себя* эти методы совершенно по-разному:
|
||||
* если вызвать несколько раз `SearchBox.search`, не дожидаясь ответа сервера, то все запросы, кроме последнего во времени, должны быть проигнорированы; даже если ответы пришли вразнобой, только тот из них, который соответствует новейшему запросу, должен быть показан в UI;
|
||||
* дополнительная задача — каким должен быть результат операции `SearchBox.search`, если она была прервана выполнением следующего запроса? Если неуспех, то в чём состоит ошибка вызывающего? Если успех, то почему результат не был отражён в UI?
|
||||
* что порождает другую проблему: а если в момент вызова `SearchBox.search` уже исполнялся какой-то запрос, инициированный пользователем — *что должно произойти*? Какой из вызовов приоритетнее — выполненный разработчиком или выполненный самим пользователем.
|
||||
|
||||
В реализации клиент-серверного API такой проблемы у нас нет — каждый актор, вызывающий функцию поиска, получит свой ответ независимо. Но с UI-компонентами этот подход не работает, поскольку все они, в конечном итоге, разделяют один общий ресурс — экран приложения и внимание пользователя на нём.
|
||||
|
||||
Любая асинхронная операция в UI-компонентах, особенно если она индицируется визуально (с помощью анимации или другого длящегося действия), может помешать любой другой визуальной операции — в том числе вследствие действий пользователя.
|
||||
|
||||
##### Сильная связность бизнес-логики и отображения
|
||||
|
||||
Посмотрим теперь на панель действий с предложениями. Допустим, мы размещаем на ней две кнопки — «заказать» и «показать на карте». Эти две кнопки выглядят одинаково и реагируют на действия пользователя одинаково — но при этом осуществляют абсолютно не имеющие ничего общего друг с другом действия.
|
||||
|
||||
Допустим, мы предоставили программисту возможность модифицировать панель действий, для чего предоставим в составе SDK класс `Button`. Достаточно быстро мы выясним, что этой функциональностью будут пользоваться в двух основных диаметрально противоположных сценариях:
|
||||
* для размещения на панели дополнительных кнопок, ну скажем, «позвонить в кафе», *выполненных в том же дизайне, что и стандартные*;
|
||||
* для изменения дизайна стандартных кнопок в соответствии с фирменным стилем заказчика, *сохраняя ту же самую функциональность в неизменном виде*.
|
||||
|
||||
Более того, возможен и третий сценарий: разработчики заходят сделать кнопку «позвонить», которая будет и выглядеть иначе, и выполнять другие действия, но при этом будет наследовать UX кнопки — т.е. нажиматься при клике, располагаться в линию с другими кнопками и так далее.
|
||||
|
||||
С точки зрения разработчика SDK это означает, что функциональная кнопка должна позволять независимо переопределять и её внешний вид, и реакцию на действия пользователя.
|
||||
|
||||
##### Двойная иерархия подчинения сущностей
|
||||
|
||||
Предположим, что разработчик хочет обогатить дизайн списка предложений иконками сетей кофеен. Если изображение известно, оно должно быть показано всюду, где происходит работа с предложением конкретной кофейни.
|
||||
|
||||
(здесь будет картинка)
|
||||
|
||||
Теперь предположим, что разработчик также переопределил внешний вид всех кнопок в SDK, добавив иконки действий:
|
||||
|
||||
(здесь будет картинка)
|
||||
|
||||
Возникает вопрос: если выбрано предложение сетевой кофейни, какая иконка должна быть на кнопке подтверждения заказа — та, что унаследована из данных предложения (логотип кофейни) или та, что унаследована от «рода занятий» самой кнопки? Элемент управления «создать заказ», таким образом, встроен в две иерархии сущностей (по визуальному отображению и по данным) и в равной степени наследует обоим.
|
||||
|
||||
#### И решения?…
|
||||
|
||||
С решением вышеуказанных проблем, увы, всё обстоит очень сложно. Мы рассмотрим некоторые подходы по разделению областей ответственности составляющих в следующих главах; но очень важно уяснить одну важную мысль: полный декаплинг, то есть разработка функционального SDK+UI, дающего разработчику свободу в переопределении и внешнего вида, и реакции на действия, и UX — невероятно дорогая в разработке задача, которая в лучшем случае утроит вашу иерархию абстракций. Универсальный совет здесь ровно один: *три раза подумайте прежде чем предоставлять возможность программной настройки UI-компонентов*. Хотя цена ошибки дизайна программных интерфейсов, как правило, не очень высока для UI-библиотек, плохо структурированный, нечитабельный и глючный SDK вряд ли может рассматриваться как сильное клиентское преимущество вашего API.
|
Loading…
x
Reference in New Issue
Block a user