Контракт REST API: Пригладим названия

от автора

А сколько у вас в компании во внутренних системах используется наименований одного и того же поля в API? А сколько способов назвать поле, которое перечисляет список id?

Я часто сталкиваюсь с тем, что при проектировании и разработке HTTP REST API команды (чаще неосознанно) собирают целый семантический и лексический зоопарк наименований. Потом бывает сложно разобраться, что нужно записать в определенное поле, или какое название поля выбрать для перечисления списка ID (например) из уже существующих.

При проектировании и разработке HTTP REST API, консистентность в именовании параметров и ресурсов является недооценненным (по моему мнению) аспектом, который влияет на понятность и удобство использования API.

Консистентность (или согласованность) означает использование одинаковых, похожих и понятных обозначений для свойств, методов и других элементов системы. Это помогает уменьшить когнитивную нагрузку:

  • при проектировании: нет необходимости каждый раз изобретать названия;

  • при чтении: стандартные свойства несут одинаковое значение в разных методах;

  • при реализации: меньше возможность опечатки, т. к. IDE (среда разработки) поможет выбирать из уже существующих переменных.

Поэтому я однажды собрала для себя и коллег гайд–чек-лист под названием “Приглаживаем названия в API” и теперь публикую его для широкой аудитории. Уверена, что он кому-то да пригодится.

Быстрый чек-лист для проверки консистентности:

  1. Свойства написаны в едином стиле

  2. Используются стандартные названия и стандартные правила наименования

  3. Названия в единственном и множественном числах согласованы

  4. Названия согласованы между несколькими слоями приложения

  5. По названию поля понятно, что в нем содержится

  6. По названию поля понятно, как оно используется

  7. Отсутствует полиморфизм в структуре

А теперь поподробнее.

1. Свойства написаны в едином стиле

Существуют разные практики грамматического оформления свойств:

  • camelCase, например "createdAt": 1320296464 ;

  • snake_case, например "created_at": "Thu Nov 03 05:19;38 +0000 2011" ;

  • PascalCase, например "DateTime": "2011-10-29T09:35:00Z" ;

  • kebab-case, например "date-time": "2011-10-29T09:35:00Z" ;

  • UPPER_CASE_SNAKE_CASE, например "UPDATE_TIME": "2011-10-29T09:35:00Z" ;

  • и другие вариации.

💡 Часто на разных слоях системы придерживаются разных стилей, но в пределах одного слоя стоит использовать один (с некоторыми оговорками) стиль.

С разработчиками из моей команды у нас такая договоренность:

  • В БД мы используем snake_case.

  • В API мы используем camelCase.

2. Используются стандартные названия и стандартные правила наименования

В большинстве систем используются одни и те же стандартные свойства, например:

  • Пагинация

    • limit, offset (Facebook)

    • page, rpp (records per page) (Twitter)

    • pageNumber, pageSize

    • start, count (LinkedIn)

  • Текстовый поиск

    • q, subString, search, text

  • Диапазоны

    • startDate, endDate

    • startAt, stopAt

    • depart_start, depart_range (количество дней +- от даты)(aviasales)

    • price_min, price_max

  • Поля в ответе

    • fields

  • Сортировка

    • sort, sortBy, sortProperty — признак сортировки

    • order, sortDirection — направление сортировки

    • sort=-age — два в одном

    • Многоуровневая сортировка: sort[name]=ASC, sort[email]=DESC

  • И много других

💡 Составьте свой словарь стандартных названий и переиспользуйте те же свойства

💡 Составьте правило формирования названий. Например, если вы возвращаете дату и время, используйте постфикс smthAt. Если указываете проценты, используйте постфикс smthPct.

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

Если не придерживаться справочника, в одном ответе JSON будет свойство pctPrice, в другом pricePct, рядом еще createdDate и createdAt и будет сложно и проектировать, и разрабатывать, и авто-тесты писать.

3. Названия в единственном и множественном числах согласованы

Сразу пример для иллюстрации:

  • Идентификатор пользователя передается как userId, а список идентификаторов как userList
    Как сказал один из системных аналитиков команды: «Выглядит очень загадочно«.

💡 Я предпочитаю использовать конструкции, в которых можно найти общий знаменатель. Например такие пары:

  • Идентификатор пользователя —userId, а список идентификаторов — userIds

  • Аналогично, например, errorCode, а список — errorCodes

Менее предпочтительные варианты, но тоже имеют место быть:

  • Идентификатор пользователя —user, а список идентификаторов — userList Однако, смущает, что по названию не понятно, идентификатор это или целый объект с данными (см. п.5 чек-листа)

  • Идентификатор пользователя —user_id, а список идентификаторов — user_id_list.
    Конструкция сложная и длинная, но ее можно использовать, если самый первый вариант не подходит для всех комбинаций, которые нужны.

4. Названия согласованы между несколькими слоями приложения

Чаще всего атрибуты в таблице сущности и в ее отражении в API схожи. 

Например, если в таблице есть поле discount_pct (скидка в процентах), логично соответствующее свойство в API назвать discountPct. Это упростит жизнь и аналитику, и тестировщику, и разработчику, и команде поддержки.

5. По названию поля понятно, что в нем содержится

Если без технической документации вам не понятно, что нужно вписать в поле, или что конкретно вы получите в ответе — стоит его поменять. Плохие примеры:

[{   "request": "N12345",   "author": "Anna" }]
  • По полю author не понятно, что в ответе будет имя. Я бы назвала свойство authorName, а еще лучше сделала бы вложенный объект author со свойствами id, name

GET /recommendations?code=1234
  • В этом случае поле code слишком абстрактное. Это пример из практики. В результате оказалось, что там нужно указать тип рекомендации.

6. По названию поля понятно, как оно используется

Этот пункт тесно связан с предыдущим, но немного о другом. Приведу пример из реального драфта технической спецификации, которую я изучала:

{    "taskId": "1",    "atLeastOne": true,    "channelIds": ["1", "2", "3", "4"]  }

Логика: Если atLeastOne = false , проверка считается финальной и результат нужно сохранить в БД.

Уже видите количество часов, которое понадобится для того, чтобы разгадать эту особенность без технической спецификации?

Контекст для тех, кто хочет разобраться

Контекст был такой: нужно проводить предварительную проверку («просто посмотреть») и финальную (зафиксировать результат). Так совпало по бизнес-требованиям, что для предпроверки нужно было проверять в режиме «хотя бы одно верно», а для финальной проверки «все верно«. Логика при проектировании так и сложилась:

  • если режим не «хотя бы одно верно» (atLeastOne = false) => значит проверка финальная => значит сохраняем в БД

В чем проблема: Поле atLeastOne по смыслу и названию означает «хотя бы один или все», а применяется как «предпроверка или финальная проверка» или «сохрани или не сохрани».

Что стоило бы сделать:

  • пусть поле atLeastOne управляет логикой проверки и говорит: «хотя бы один или все»

  • пусть доп. поле isFinal управляет режимом проверки и говорит: «предпроверка или финальная проверка»

  • или доп. поле saveResult управляет результатом проверки и говорит: «сохрани или не сохрани»

💡 Делайте так, чтобы ваши поля говорили сами за себя.

7. Отсутствует полиморфизм в структуре

Что такое полиморфизм? Вернемся к одному из предыдущих примеров. Например, содержимое ответа на запрос GET /requests ответ выглядит так:

{   "request": "1",   "author": "Anna" }

А ответ на запрос GET /requests/1 так:

{   "request": "1",   "author": {     "id": "123",     "name": "Anna"   } }

💡 Это полиморфизм структуры ресурса (в данном случае вложенного):

  • в одном случае поле author  — это строка с именем;

  • в другом случае поле author — это объект с вложенными данными.

Такой ситуации стоит избегать. Если один и тот же объект используется во многих эндпоинтах, я рекомендую его сразу заключать в объект, как в GET /titles/1

Иногда так делают, чтобы просто не возвращать все данные, укоротить ответ. Но в таком случае стоит отдельным параметром (да или ладно, просто в коде) управлять списком полей в ответе (если не во всех ответах требуются все из них).

Подробнее про объекты

Чем структурированнее будет ответ, тем проще с ним будет работать. О том как выделять объекты приводила примеры в разделе Шаг 4. Формирование ответного JSON статьи:

GET запросы на практике: правила, принципы и примеры
Я думаю, что вы не раз уже гуглили, заглядывали в статьи, манифесты IT-гигантов о лучших практиках п…

habr.com

P. S. Я бы хотела, чтобы эта статья дополнялась и была живой, поэтому если у вас есть свои стандарты и предложения по наименованиям и правилам, оставляйте их в комментариях, будем вместе пополнять рекомендации.


ссылка на оригинал статьи https://habr.com/ru/articles/860010/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *