А сколько у вас в компании во внутренних системах используется наименований одного и того же поля в 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. Я бы хотела, чтобы эта статья дополнялась и была живой, поэтому если у вас есть свои стандарты и предложения по наименованиям и правилам, оставляйте их в комментариях, будем вместе пополнять рекомендации.

Комментарии (3)


  1. Samr1
    27.12.2024 07:44

    Спасибо за труд! Полезный гайд, стандартизация сильно экономит время. А как вы решаете эту задачу между командами?

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

    Мы придерживаемся стиля snake_case, т.к. при чтении меньше когнитивная нагрузка. Но разделение стилей БД с API  - хорошая идея.


    1. WayMax
      27.12.2024 07:44

      Но разделение стилей БД с API  - хорошая идея.

      Почему?


      1. AnnaMozer Автор
        27.12.2024 07:44

        я придерживаюсь такого разделения на основании документации по технологиям

        Например если заглянуть в официальную доку postgresql, там все аттрибуты в БД указаны через snake_case. И раз авторы технологии так указали, я решила следовать этому стилю при работе с базой

        во всех официальных туториалах javascript переменные в основном называются через camelCase, поэтому при работе с JSON (javascript нотация ведь), придерживаюсь такого стиля

        ну а где JSON в API, там и остальные параметры, поэтому их в ту же корзинку

        не претендую, конечно, на истину, но у меня были такие размышления