Привет, Хабр! Прежде чем я начну, стоит немного рассказать о себе. Я не так давно занимаюсь серьезной разработкой (всего года три) и, как мне кажется, именно отсутствие многолетнего опыта часто позволяет мне смотреть на многие традиции и устоявшиеся способы выполнения стандартных задач с оригинальной точки зрения. Моя специальность — бэкенд сайтов и мобильных приложений (основной язык разработки — python3, любимый фреймворк — Django).

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

Причиной написания данной статьи послужил спор с моим коллегой по поводу именования сущностей REST: он утверждал, что необходимо называть их во множественном числе, например, books, я же считал (и считаю), что это нелогично, и отстаивал вариант book. Признаюсь, мы оба пошли неправильным путем — вместо того, чтобы найти оригинальную документацию к архитектуре REST, написанную её создателем Р. Филдингом ещё в 2000 году (ссылку приведу в конце), мы сначала начали придумывать логические обоснования нашим позициям, а после просто стали смотреть, как это делается в больших компаниях и, перебрав несколько вариантов, установили, что мой встречается чаще, на чем и порешили спор завершить.

Всё бы ничего, но эта же дискуссия повторилась месяц спустя с другим коллегой, который открыл документацию к создаваемому мной API и очень удивился моему способу именования. Кратко описав ему ранее приведенные аргументы, я понял, что это будет повторяться вечно, если я не разберусь, как правильно называть ресурсы.

Для начала кратко перечислю основные аргументы в пользу обоих вариантов.

За единственное число:


За множественное число:


Как вы можете видеть, аргументы довольно противоречивы, и к каждому можно придумать сотню контраргументов. Добавлю, что ссылаться на чью-то реализацию я вообще считаю достаточно бессмысленным занятием: да, Atlassian и Twitter — гиганты IT, но ведь все могут ошибаться, особенно учитывая, что делают все по-разному.

Заниматься составлением огромных списков ресурсов, которые используют разные варианты именования ресурсов REST, мне совершенно не хочется. Вместо этого я пошел читать диссертацию Филдинга, в которой определенных конвенций именования нет. Зато есть четкая формулировка концепции «resource identifier», которая, собственно, и является предметом дискуссии. Вот что пишет о ней Филдинг:

REST uses a resource identifier to identify the particular resource involved in an interaction between components. REST connectors provide a generic interface for accessing and manipulating the value set of a resource, regardless of how the membership function is defined or the type of software that is handling the request. The naming authority that assigned the resource identifier, making it possible to reference the resource, is responsible for maintaining the semantic validity of the mapping over time (i.e., ensuring that the membership function does not change).

Ну то есть по Филдингу есть некая naming authority, которая и занимается присвоением идентификаторов ресурсам, и она-то как раз и ответственна за семантическую правильность наименований. Никаких гайдлайнов по этой самой семантической правильности Филдинг нам не дал, веря, что логики достаточно. Если поискать ещё, можно найти его комментарий по похожему вопросу, который показывает всю суть его отношения к любым уточнениям и стандартизациям REST:

In regards to «untold hours wasted in debate», that is close to the goal of my dissertation — to introduce a way of thinking about software architecture that promotes honest debate about the properties that are desired and actual thought applied to the constraints chosen to achieve them (and their resulting trade-offs). It is almost never wasted, even when it is poorly informed.

...at best all you will get is a committee that has some vague notion
of what they consider to be design to write down the least common
denominator of misinformed «best practice» based upon whatever Microsoft
chose to implement in its last release. The IETF has made a habit of
that recently.

То есть, если совсем обобщать — «думайте сами, решайте сами, а если это стандартизировать, то получится бред и обязаловка». Ну что ж, вообще говоря, такой подход к этому вопросу гораздо лучше, чем форсинг надуманных стандартов и спецификаций, хоть он и вызывает появление множества различных реализаций и подходов, но — не есть ли это признак действительно «free-as-in-free-speech» архитектуры?

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

  1. В оригинальном описании REST концепция «resource identifier» описывается как уникальный идентификатор, однозначно указывающий на ресурс. Что это за идентификатор — ID, название или хэш строкового представления — не уточняется, этот вопрос оставлен на усмотрение разработчиков с единственным условием: поддержание семантической целостности архитектуры на протяжении всего существования системы.

  2. Необходимо делать архитектуру понятной для пользователя. Внутренние особенности реализации — такие, как название таблицы в БД или имя сущности в описании моделей ORM — должны отходить на второй план, а в идеале вообще не учитываться.

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

Спасибо за внимание!

Источники:

  1. Диссертация Роя Филдинга, глава 5
  2. Википедия
  3. Комментарий самого Филдинга по сходному вопросу
  4. Документация Atlassian
  5. Документация Twitter
Поделиться с друзьями
-->

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


  1. tytar
    16.06.2017 14:56
    +2

    1. feakuru
      17.06.2017 00:00
      -2

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

      … существительные во множественном числе. Хуже — в единственном числе.

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


  1. oxidmod
    16.06.2017 15:08
    +7

    imho, если у нас есть коллекция, то множественное число
    /clients - все клиенты или отфильтрованная коллекция по фильтрам в гет параметрах
    /clients/client_id - конкретный клиент


    Если же ресурсы не складываются в коллекцию, то единственное число
    /clients/client_id/company - компания к которой привязан данный клиент

    Если может быть привязан к нескольким одновременно
    /clients/client_id/companies
    /clients/client_id/companies/company_id


    В обоих случаях может быть что-то типа:
    /companies
    /companies/company_id


  1. AlexPu
    16.06.2017 16:23
    +3

    >>Таблица в БД, скорее всего, называется во множественном числе!

    Я закончил читать на этой фразе — если до нее я еще на что-то надеялся, то это уже стало самой последней каплей


    1. Eldhenn
      16.06.2017 18:23

      Вот я тоже был сильно удивлён и даже немного взволнован этой фразой.


    1. Skycaptain
      16.06.2017 23:57
      +1

      вообще, это достаточно распространенный вариант именования таблиц


      1. AlexPu
        17.06.2017 10:24
        -1

        т.е. если вы так делаете, то это «распространенный вариант»?


        1. sl_bug
          17.06.2017 10:36

          Много кто так делает. Для RoR это стандарт например. И это логично, т.к. в таблице хранятся книги, а не книга.


          1. sl_bug
            17.06.2017 10:44

            In his book «SQL Programming Style», Joe Celko suggests that a collection (e.g. a table) should be named in the plural, while a scalar data element (e.g. a column) should be named in the singular. He cites ISO-11179-4 as a standard for metadata naming, which supports this guideline. Table is a set.


          1. VolCh
            18.06.2017 08:46

            Но работаем мы с записями, а не с таблицами, user.id = person.user_id выглядит однозначные чем users.id = persons.id.


            1. sl_bug
              18.06.2017 10:21

              Почему однозначнее? И работаем мы не с записями, а с таблицами (говорим по каким полям объеденять таблицы, по каким фильтровать содержимое таблиц и тд), и результат выборки у нас это тоже set.


              1. VolCh
                18.06.2017 11:03

                У меня нет особых вариантов как трактовать запись "user.id" кроме как "идентификатор пользователя", а вот для записи "users.id" могут быть варианты, например, "множество идентификаторов пользователей".


                Мы говорим по каким полям записей объединять их, фильтровать и т. п. Результат выборки это множество, источник — множество, а вот условия объединения, фильтрации и т. п. работают на уровне записей.


                1. sl_bug
                  18.06.2017 14:26

                  При виде users.id я четко вижу что users название таблицы, а id колонки. Всё остальное не нужно. Есть рекомендации одного из разработчиков стандарта SQL, есть ISO, а вы придумываете чушь.


                  1. ghost404
                    18.06.2017 16:22

                    А можно пруфлинк?


                    Ваша логика на самом деле ни чуть не лучше.


                    1. В программах мы оперируем конкретными сущностями. Пользователей в приложении много, но мы всегда создаем конкретный объект User.
                    2. Маппить сущность User на таблицу users немножко странно на мой взгляд.
                    3. В объектной модели мы имеем User.id, а в БД users.id. Также несогласованность на лицо.
                    4. Если мы немножечко вспомним правила английского языка, то выяснится, что множественное число не всегда формируется добавлением суффикса S. Иногда это ES.
                    5. Есть слова которые в множественное форме пишутся иначе (пруф):
                      baby -> babies
                      knife -> knives
                      person -> people
                      и тд
                    6. Есть слова которые не изменяются в множественной форме: sheep, series, deer, fish и тд

                    От сюда вывод. Чтоб не путать себя и других, иногда лучше использовать единственное число, а не мишашину из разных форм.


                    1. sl_bug
                      18.06.2017 16:27

                      читайте https://www.amazon.com/Celkos-Programming-Kaufmann-Management-Systems/dp/0120887975

                      1. да потому что наш объект это одна запись
                      2. ни капельки
                      3. аналогично
                      4. и что?
                      5. опять же в чем проблема?
                      6. см выше

                      вывод — использовать множественное число, т.к. это логично.


                      1. sl_bug
                        18.06.2017 16:32

                        на счет пункта 2 — мы маппим сущность User, не на таблицу, а на строку в таблице.

                        на счет множественного числа

                        > helper.pluralize(2, 'person')
                        => "2 people"
                        


                        всё придумано до нас, и правила перевода из единственного во множественное тоже


                        1. ghost404
                          18.06.2017 18:47

                          на счет пункта 2 — мы маппим сущность User, не на таблицу, а на строку в таблице.

                          Нет. Мапим мы как раз на таблицу и поля сущности на колонки таблицы.


                          всё придумано до нас, и правила перевода из единственного во множественное тоже

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


                          1. sl_bug
                            18.06.2017 21:00
                            +1

                            Маппим мы на запись. На таблицу мы мы маппим репозиторий — UsersRepository.

                            Баги всегда бывают, это не повод не использовать какие-либо фичи. В RoR добавить исключение на слово дело 5 секунд — одна строчка в конфиге.


                            1. ghost404
                              19.06.2017 10:14

                              А, ну так теперь понятно откуда ноги растут. Оказывается все дело в RoR, который просто не дает именовать таблицы иначе)))


                              Маппим мы на запись. На таблицу мы мы маппим репозиторий — UsersRepository. 

                              А если серьезно, то маппинг описывается либо в сущности 1, 2, 3 либо в конфиг файлах 1, 2, 3.


                              А заполнение полей сущности из набора данных полученных от БД, выполняется в недрах движка БД. В случае Active Record, заполнение выполняется в самой сущности.


                              1. VolCh
                                19.06.2017 11:57

                                А, ну так теперь понятно откуда ноги растут. Оказывается все дело в RoR, который просто не дает именовать таблицы иначе)))

                                Задолго до появления RoR в MySQL служебные таблицы именовались в единственном числе, например: mysql.user — таблица пользователей.


                                1. ghost404
                                  19.06.2017 12:41

                                  Это был камень в огород RoR. Нормальные движки БД, не накладывают таких ограничений на БД.


                                  1. VolCh
                                    19.06.2017 12:59

                                    Думаю, RoR позволяет называть таблицы во множественном числе, просто по умолчанию называет их соответственно имени класса, которое обычно в единственном числе.


                                    1. ghost404
                                      19.06.2017 15:14

                                      Наоборот


                                      Rails will pluralize your class names to find the respective database table. So, for a class Book, you should have a database table called books.

                                      То есть нужно менять поведение RoR по умолчанию, чтоб использовать имена таблиц в единственном числе как и имена классов.


                                      1. sl_bug
                                        19.06.2017 15:22

                                        Это меняется одной строчкой в модели. self.table_name = 'что-угодно', так что всё позволяет. Вопрос зачем?


                                        1. ghost404
                                          19.06.2017 16:49

                                          Так об том и речь. Для вас это зачем?, так как RoR вас принуждает использовать множественную форму. И для вас это естественно и логично. И все остальные мнения для вас кажутся неправильными, что вполне ожидаем.


                                          А все остальные используют нормальные движки и называют таблицы так как им удобно/логично/принято в проекте/исторически сложилось. От этого и возникает это разногласие. Вам надо просто понять что RoR, это не истина в последней инстанции. Есть и другие миры со своими правилами и законами.


                                          Хотя на самом деле, вам этого не надо. Вам и в вашем мирке неплохо живется ;)


                  1. VolCh
                    18.06.2017 16:34

                    Это не я придумываю, это одна из распространенных практик именования таблиц. То, что она вам не нравится не даёт вам права называть её чушью, даже если та, которая вам нравится, станет частью стандарта SQL.


                    В теории РСУБД вообще таблиц нет, есть отношения. Выполняя команду CREATE TABLE user (id INT, name VARCHAR) мы не создаём хранилище пользователей, мы создаём заголовок (схему) для отношения атрибутов id и name. Один заголовок, одно отношение. Зачем его именовать во множественном числе? Тело отношения состоит из множества кортежей(строк), но отношение оно одно, заголовок у него один. Зачем множественное число? Оно как раз лишнее.


                    1. sl_bug
                      18.06.2017 17:16

                      Вот теперь мне стало понятно зачем вам в единственном числе, но у меня есть прекрасный пример как это можно сделать правильно на примере postgres

                      CREATE TYPE user AS (id INT, name VARCHAR);
                      CREATE TABLE users OF user (PRIMARY KEY (id));
                      


                      1. VolCh
                        19.06.2017 11:55

                        Лучше выглядит, да. Но работать же ней всё равно типа SELECT * FROM users WHERE user.name = 'root'?


        1. Skycaptain
          17.06.2017 10:39
          +1

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


  1. TimsTims
    16.06.2017 20:58
    +6

    Я уж думал, проблема в каком то основополагающем принципе API, в механизме каком то, а автор всерьез спорит добавлять ли букву S к методам и таблицам… Видимо, делать на работе больше нечего, а книг хороших не продают, интернет платный только на разрешенные сайты


    1. feakuru
      17.06.2017 00:05
      -3

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


  1. kroshanin
    16.06.2017 21:11
    +1

    Ставить в конце букву S или нет — дело лишь вкуса. Главное, чтобы у вас все было в едином стиле.
    А то у ройстата, например, чтобы добавить заказ нужно вызвать "/project/add-orders", чтобы получить список заказов "/project/integration/order/list", а чтобы обновить статус заказа вообще "/project/integration/order/{orderId}/status/update". Не надо так делать.


  1. edge790
    16.06.2017 23:57
    +5

    это, по-вашему, get /book должен вернуть все книги?
    REST можно воспринимать как файловую систему:

    • /books — содержит все книги
    • /books/1 — содержимое первой книги
    • /books/1/index — содержит оглавление первой книги

    В этом подходе все кажется логичным


  1. zezic
    16.06.2017 23:57
    -1

    Думаю, нужно всё всегда писать в единственном числе, чтобы лишний раз об этом не думать. Даже если речь идёт о таблицах в БД, то пусть лучше будут в единственном. Минимум переопределений при конструировании врапперов для API, конструкторов URL, минимум переопределений при настройке ORM. Лучше уступить удобству сэкономив на эстетике.


    1. VolCh
      18.06.2017 08:41
      -1

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


  1. sl_bug
    17.06.2017 02:13
    +2

    1.
    GET /profile
    PUT /profile
    DELETE /profile

    2.
    GET /profiles
    POST /profiles
    GET /profiles/1
    PUT /profiles/1
    DELETE /profiles/1

    И вот становится всё логично — в первом случае я работаю со своим профилем(который один, по этому в единственном числе), во втором с коллекцией (по этому во множественном)


    1. Johanga
      18.06.2017 00:55

      Именно. Почему автор считает, что «books» не могут быть уникальным идентификатором ресурса не понятно. Ресурс — книги.


      1. sl_bug
        18.06.2017 01:40

        Это либо риторический вопрос, либо не ко мне :)


  1. J_K
    19.06.2017 01:04

    Это все неважно.