Ещё 8 правил проектирования API

    1. Используйте глобально уникальные идентификаторы

    Хороший тон при разработке API — использовать для идентификаторов сущностей глобально уникальные строки, либо семантичные (например, "lungo" для видов напитков), либо случайные (например UUID-4). Это может чрезвычайно пригодиться, если вдруг придётся объединять данные из нескольких источников под одним идентификатором.

    Мы вообще склонны порекомендовать использовать идентификаторы в urn-подобном формате, т.е. urn:order:<uuid> (или просто order:<uuid>), это сильно помогает с отладкой legacy-систем, где по историческим причинам есть несколько разных идентификаторов для одной и той же сущности — тогда неймспейсы в urn помогут быстро понять, что это за идентификатор и нет ли здесь ошибки использования.

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

    NB: в этой книге часто используются короткие идентификаторы типа "123" в примерах — это для удобства чтения на маленьких экранах, повторять эту практику в реальном API не надо.

    2. Клиент всегда должен знать полное состояние системы

    Правило можно ещё сформулировать так: не заставляйте клиент гадать.

    Плохо:

    // Создаёт заказ и возвращает его id
    POST /v1/orders
    { … }
    →
    { "order_id" }
    // Возвращает заказ по его id
    GET /v1/orders/{id}
    // Заказ ещё не подтверждён
    // и ожидает проверки
    → 404 Not Found

    — хотя операция будто бы выполнена успешно, клиенту необходимо самостоятельно запомнить идентификатор заказа и периодически проверять состояние GET /v1/orders/{id}. Этот паттерн плох сам по себе, но ещё и усугубляется двумя обстоятельствами:

    • клиент может потерять идентификатор, если произошёл системный сбой в момент между отправкой запроса и получением ответа или было повреждено (очищено) системное хранилище данных приложения;

    • потребитель не может воспользоваться другим устройством; фактически, знание о сделанном заказе привязано к конкретному юзер-агенту.

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

    Хорошо:

    // Создаёт заказ и возвращает его
    POST /v1/orders
    { <параметры заказа> }
    →
    {
      "order_id",
      // Заказ создаётся в явном статусе
      // «идёт проверка»
      "status": "checking",
      …
    }
    // Возвращает заказ по его id
    GET /v1/orders/{id}
    →
    { "order_id", "status" … }
    // Возвращает все заказы пользователя
    // во всех статусах
    GET /v1/users/{id}/orders

    3. Избегайте двойных отрицаний

    Плохо: "dont_call_me": false
    — люди в целом плохо считывают двойные отрицания. Это провоцирует ошибки.

    Лучше: "prohibit_calling": true или "avoid_calling": true
    — читается лучше, хотя обольщаться все равно не следует. Насколько это возможно откажитесь от семантически двойных отрицаний, даже если вы придумали «негативное» слово без явной приставки «не».

    Стоит также отметить, что в использовании законов де Моргана ошибиться ещё проще, чем в двойных отрицаниях. Предположим, что у вас есть два флага:

    GET /coffee-machines/{id}/stocks
    →
    {
      "has_beans": true,
      "has_cup": true
    }
    

    Условие «кофе можно приготовить» будет выглядеть как has_beans && has_cup — есть и зерно, и стакан. Однако, если по какой-то причине в ответе будут отрицания тех же флагов:

    {
      "beans_absence": false,
      "cup_absence": false
    }
    

    — то разработчику потребуется вычислить флаг !beans_absence && !cup_absence!(beans_absence || cup_absence), а вот этом переходе ошибиться очень легко, и избегание двойных отрицаний помогает слабо. Здесь, к сожалению, есть только общий совет «избегайте ситуаций, когда разработчику нужно вычислять такие флаги».

    4. Избегайте неявного приведения типов

    Этот совет парадоксально противоположен предыдущему. Часто при разработке API возникает ситуация, когда добавляется новое необязательное поле с непустым значением по умолчанию. Например:

    POST /v1/orders
    {}
    →
    {
      "contactless_delivery": true
    }

    Новая опция contactless_delivery является необязательной, однако её значение по умолчанию — true. Возникает вопрос, каким образом разработчик должен отличить явное нежелание пользоваться опцией (false) от незнания о её существовании (поле не задано). Приходится писать что-то типа такого:

    if (Type(order.contactless_delivery) == 'Boolean' &&
        order.contactless_delivery == false) { … }

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

    В этих ситуациях универсальное правило — все новые необязательные булевы флаги должны иметь значение по умолчанию false.

    Хорошо

    POST /v1/orders
    {}
    →
    {
      "force_contact_delivery": false
    }

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

    Плохо:

    // Создаёт пользователя
    POST /users
    { … }
    →
    // Пользователи создаются по умолчанию
    // с указанием лимита трат в месяц
    {
      …
      "spending_monthly_limit_usd": "100"
    }
    // Для отмены лимита требуется
    // указать значение null
    POST /users
    { 
      …
      "spending_monthly_limit_usd": null
    }

    Хорошо

    POST /users
    {
      // true — у пользователя снят
      //   лимит трат в месяц
      // false — лимит не снят
      //   (значение по умолчанию)
      "abolish_spending_limit": false,
      // Необязательное поле, имеет смысл
      // только если предыдущий флаг
      // имеет значение false
      "spending_monthly_limit_usd": "100",
      …
    }

    NB: противоречие с предыдущим советом состоит в том, что мы специально ввели отрицающий флаг («нет лимита»), который по правилу двойных отрицаний пришлось переименовать в abolish_spending_limit. Хотя это и хорошее название для отрицательного флага, семантика его довольно неочевидна, разработчикам придётся как минимум покопаться в документации. Таков путь.

    5. Избегайте частичных обновлений

    Плохо:

    // Возвращает состояние заказа
    // по его идентификатору
    GET /v1/orders/123
    →
    {
      "order_id",
      "delivery_address",
      "client_phone_number",
      "client_phone_number_ext",
      "updated_at"
    }
    // Частично перезаписывает заказ
    PATCH /v1/orders/123
    { "delivery_address" }
    →
    { "delivery_address" }

    — такой подход часто практикуют для того, чтобы уменьшить объёмы запросов и ответов, плюс это позволяет дёшево реализовать совместное редактирование. Оба этих преимущества на самом деле являются мнимыми.

    Во-первых, экономия объёма ответа в современных условиях требуется редко. Максимальные размеры сетевых пакетов (MTU, Maximum Transmission Unit) в настоящее время составляют более килобайта; пытаться экономить на размере ответа, пока он не превышает килобайт — попросту бессмысленная трата времени.

    Перерасход трафика возникает, если:

    • не предусмотрен постраничный перебор данных;

    • не предусмотрены ограничения на размер значений полей;

    • передаются бинарные данные (графика, аудио, видео и т.д.).

    Во всех трёх случаях передача части полей в лучше случае замаскирует проблему, но не решит. Более оправдан следующий подход:

    • для «тяжёлых» данных сделать отдельные эндпойнты;

    • ввести пагинацию и лимитирование значений полей;

    • на всём остальном не пытаться экономить.

    Во-вторых, экономия размера ответа выйдет боком как раз при совместном редактировании: один клиент не будет видеть, какие изменения внёс другой. Вообще в 9 случаях из 10 (а фактически — всегда, когда размер ответа не оказывает значительного влияния на производительность) во всех отношениях лучше из любой модифицирующей операции возвращать полное состояние сущности в том же формате, что и из операции доступа на чтение.

    В-третьих, этот подход может как-то работать при необходимость перезаписать поле. Но что делать, если поле требуется сбросить к значению по умолчанию? Например, как удалить client_phone_number_ext?

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

    Хорошо: можно применить одну из двух стратегий.

    Вариант 1: разделение эндпойнтов. Редактируемые поля группируются и выносятся в отдельный эндпойнт. Этот подход также хорошо согласуется с принципом декомпозиции, который мы рассматривали в предыдущем разделе.

    // Возвращает состояние заказа
    // по его идентификатору
    GET /v1/orders/123
    →
    {
      "order_id",
      "delivery_details": {
        "address"
      },
      "client_details": {
        "phone_number",
        "phone_number_ext"
      },
      "updated_at"
    }
    // Полностью перезаписывает
    // информацию о доставке заказа
    PUT /v1/orders/123/delivery-details
    { "address" }
    // Полностью перезаписывает
    // информацию о клиенте
    PUT /v1/orders/123/client-details
    { "phone_number" }

    Теперь для удаления client_phone_number_ext достаточно не передавать его в PUT client-details. Этот подход также позволяет отделить неизменяемые и вычисляемые поля (order_id и updated_at) от изменяемых, не создавая двусмысленных ситуаций (что произойдёт, если клиент попытается изменить updated_at?). В этом подходе также можно в ответах операций PUT возвращать объект заказа целиком (однако следует использовать какую-то конвенцию именования).

    Вариант 2: разработать формат описания атомарных изменений.

    POST /v1/order/changes
    X-Idempotency-Token: <токен идемпотентности>
    {
      "changes": [{
        "type": "set",
        "field": "delivery_address",
        "value": &lt;новое значение>
      }, {
        "type": "unset",
        "field": "client_phone_number_ext"
      }]
    }

    Этот подход существенно сложнее в имплементации, но является единственным возможным вариантом реализации совместного редактирования, поскольку он явно отражает, что в действительности делал пользовать с представлением объекта. Имея данные в таком формате возможно организовать и оффлайн-редактирование, когда пользовательские изменения накапливаются и потом сервер автоматически разрешает конфликты, «перебазируя» изменения.

    6. Избегайте неатомарных операций

    С применением массива изменений часто возникает вопрос: что делать, если часть изменений удалось применить, а часть — нет? Здесь правило очень простое: если вы можете обеспечить атомарность, т.е. выполнить либо все изменения сразу, либо ни одно из них — сделайте это.

    Плохо:

    // Возвращает список рецептов
    GET /v1/recipes
    →
    {
      "recipes": [{
        "id": "lungo",
        "volume": "200ml"
      }, {
        "id": "latte",
        "volume": "300ml"
      }]
    }
    
    // Изменяет параметры
    PATCH /v1/recipes
    {
      "changes": [{
        "id": "lungo",
        "volume": "300ml"
      }, {
        "id": "latte",
        "volume": "-1ml"
      }]
    }
    → 400 Bad Request
    
    // Перечитываем список
    GET /v1/recipes
    →
    {
      "recipes": [{
        "id": "lungo",
        // Это значение изменилось
        "volume": "300ml"
      }, {
        "id": "latte",
        // А это нет
        "volume": "300ml"
      }]
    }

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

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

    Лучше:

    PATCH /v1/recipes
    {
      "changes": [{
        "recipe_id": "lungo",
        "volume": "300ml"
      }, {
        "recipe_id": "latte",
        "volume": "-1ml"
      }]
    }
    // Можно воспользоваться статусом
    // «частичного успеха», если он предусмотрен
    // протоколом
    → 200 OK
    {
      "changes": [{
        "change_id",
        "occurred_at",
        "recipe_id": "lungo",
        "status": "success"
      }, {
        "change_id",
        "occurred_at",
        "recipe_id": "latte",
        "status": "fail",
        "error"
      }]
    }

    Здесь:

    • change_id — уникальный идентификатор каждого атомарного изменения;

    • occurred_at — время проведения каждого изменения;

    • error — информация по ошибке для каждого изменения, если она возникла.

    Не лишним будет также:

    • введение sequence_id в запросе, чтобы гарантировать порядок исполнения операций и соотнесение порядка статусов изменений в ответе с запросом;

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

    Неатомарные изменения нежелательны ещё и потому, что вносят неопределённость в понятие идемпотентности, даже если каждое вложенное изменение идемпотентно. Рассмотрим такой пример:

    PATCH /v1/recipes
    {
      "idempotency_token",
      "changes": [{
        "recipe_id": "lungo",
        "volume": "300ml"
      }, {
        "recipe_id": "latte",
        "volume": "400ml"
      }]
    }
    → 200 OK
    {
      "changes": [{
        …
        "status": "success"
      }, {
        …
        "status": "fail",
        "error": {
          "reason": "too_many_requests"
        }
      }]
    }

    Допустим, клиент не смог получить ответ и повторил запрос с тем же токеном идемпотентности.

    PATCH /v1/recipes
    {
      "idempotency_token",
      "changes": [{
        "recipe_id": "lungo",
        "volume": "300ml"
      }, {
        "recipe_id": "latte",
        "volume": "400ml"
      }]
    }
    → 200 OK
    {
      "changes": [{
        …
        "status": "success"
      }, {
        …
        "status": "success",
      }]
    }

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

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

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

    7. Соблюдайте правильный порядок ошибок

    Во-первых, всегда показывайте неразрешимые ошибки прежде разрешимых:

    POST /v1/orders
    {
      "recipe": "lngo",
      "offer"
    }
    → 409 Conflict
    {
      "reason": "offer_expired"
    }
    // Повторный запрос
    // с новым `offer`
    POST /v1/orders
    {
      "recipe": "lngo",
      "offer"
    }
    → 400 Bad Request
    {
      "reason": "recipe_unknown"
    }
    

    — какой был смысл получать новый offer, если заказ все равно не может быть создан?

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

    Плохо:

    POST /v1/orders
    {
      "items": [{ "item_id": "123", "price": "0.10" }]
    }
    →
    409 Conflict
    {
      "reason": "price_changed",
      "details": [{ "item_id": "123", "actual_price": "0.20" }]
    }
    // Повторный запрос
    // с актуальной ценой
    POST /v1/orders
    {
      "items": [{ "item_id": "123", "price": "0.20" }]
    }
    →
    409 Conflict
    {
      "reason": "order_limit_exceeded",
      "localized_message": "Лимит заказов превышен"
    }

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

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

    // Создаём заказ с платной доставкой
    POST /v1/orders
    {
      "items": 3,
      "item_price": "3000.00"
      "currency_code": "MNT",
      "delivery_fee": "1000.00",
      "total": "10000.00"
    }
    → 409 Conflict
    // Ошибка: доставка становится бесплатной
    // при стоимости заказа от 9000 тугриков
    {
      "reason": "delivery_is_free"
    }
    
    // Создаём заказ с бесплатной доставкой
    POST /v1/orders
    {
      "items": 3,
      "item_price": "3000.00"
      "currency_code": "MNT",
      "delivery_fee": "0.00",
      "total": "9000.00"
    }
    → 409 Conflict
    // Ошибка: минимальная сумма заказа
    // 10000 тугриков
    {
      "reason": "below_minimal_sum",
      "currency_code": "MNT",
      "minimal_sum": "10000.00"
    }

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

    8. Отсутствие результата — тоже результат

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

    Плохо

    POST /search
    {
      "query": "lungo",
      "location": &lt;положение пользователя>
    }
    → 404 Not Found
    {
      "localized_message":
        "Рядом с вами не делают лунго"
    }

    Статусы 4xx означают, что клиент допустил ошибку; однако в данном случае никакой ошибки сделано не было, ни пользователем, ни разработчиком: клиент же не может знать заранее, готовят здесь лунго или нет.

    Хорошо:

    POST /search
    {
      "query": "lungo",
      "location": &lt;положение пользователя>
    }
    → 200 OK
    {
      "results": []
    }
    

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


    Это дополнение к главе 11 книги о разработке API. Работа ведётся на Github. Англоязычный вариант этой же главы опубликован на medium. Я буду признателен, если вы пошарите его на реддит — я сам не могу согласно политике платформы.

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 94

      0
      Очень полезная статья! Сам писал такое когда-то, но тема всегда актуальна!
      Только оформление немного подкачало: где-то заголовки не оформлены, где-то еще какие-то мелочи, не хватает сжатого изложения (можно было tldr секцию сделать, или примеры скрыть в раскрывающиеся блоки).
        0

        Заголовки я поправил, а как спойлеры делать в новом редакторе я не нашёл.

        0
        Статусы 4xx означают, что клиент допустил ошибку

        Апологеты REST с вами не согласятся.
        404 — означает, что ресурс не найден, а причина, почему он не найден может приходить теле.
        Возвращать 404 если в базе по какой-то причине отсутствует объект — это очень популярный подход в REST.
          +4
          Именно — если _ресурс_ не найден — то, как по мне, ничего плохого в 404 нет.

          В примере автора же — поиск, который не какой-то конкретный ресурс, которого может не быть. Он есть всегда и всегда успешен, даже если ничего не найдено — «ресурсом» в контексте REST в данном случае будет «результат поиска» (а он может быть и пустым), а не его содержимое. Пользователь/клиент здесь не ошибся. А вот попытка загрузить данные с URI несуществующего ресурса — вполне ошибка, поэтому 404.
            0
            В примере автора же — поиск, который не какой-то конкретный ресурс, которого может не быть

            Поиск это ресурс, тоже. С собственной семантикой и представлением.

              0
              Тут вопрос терминологии. Я в этом контексте под «ресурсом» имел в виду «сущность», но, правда, не написал про отличие сущности от «фичи».

              Можно сказать, что поиск — это ресурс со своей семантикой, и отличие тут как раз в том, что если какой-нибудь заказ (/orders/{order_id}) — это ресурс-сущность, поиск — это ресурс-фича, так что он всегда доступен.
                0
                Согласен с вами. Дополню ещё, что обработку 4хх ошибок приходится дополнительно пилить на клиенте, в отдельном обработчике «ошибок», которые не всегда ошибки, пока не заглянешь внутрь. Из-за этого возникает странный оверхэд по коду на пустом месте, потому-что кому-то захотелось поиспользовать HTTP-ответы везде, где это возможно.
                  0

                  А можно пример не совсем ошибки?

                    0

                    Да вот пожалуйста: юзер не найден, заказ не найден, платеж не найден, недостаточно доступа к какой-то части функции (403?) — можно менять аватар, но нельзя никнейм (нужно пояснять 403)

                      0

                      Немного поясню: юзер не найден, потому что такого юзера нет, либо этот юзер заблокирован, либо этот юзер из другой группы (например когда в REST указываешь сразу 2 параметра — ид сервера и Ид пользователя), или по точному совпадению никого не найдено, но вот вам в body возможные совпадения. Ну и конечно же, 404 когда просто дёргается неправильный url.

                        0

                        Для меня это всё обычные ошибки. Можно пояснять в теле, что не так или рекомендации какие-то давать. А когда неверный урл, то не то что обычная (пользователь ввёл что-то не то), а критическая или типа того — код неправильно написан.

                          0

                          Просто апи на то и апи, что должен поддерживать несколько разных клиентов, а у большинства из них дефолтное поведение — выдавать результат в виде ошибки, когда пришел ответ 4хх. Вот и приходится в них кодить обработку и 4хх и 200.

                            0

                            Это у какого большинства?

            +2

            Выше правильно ответили. Но должен заметить, что REST никак не регламентирует такие вопросы, это семантика самого HTTP.

            0

            Хорошая статья, хочу больше.
            Хотя совет "Избегайте частичных обновлений" на мой взгляд слишком категоричный. В реальном мире встречаются и другие причины поддерживать патч, кроме перечисленных. Например, забота о клиенте. Представьте, что вы должны каждый раз передать представление из 50 полей для того, что поменять одно-два (а остальные 48 полей в вашем конкретном сценарии вам не очень-то и интересны)

              +3

              48 полей — это уже проблема само по себе, такие объекты надо декомпозировать.
              Но вообще с т.з. разработчика клиента как раз удобнее все 48 посылать, чем выбирать изменившиеся.


              Хорошая статья, хочу больше.

              Дык собсно https://twirl.github.io/The-API-Book/docs/API.ru.html#chapter-11

              +1
              NB: в этой книге часто используются короткие идентификаторы типа «123» в примерах — это для удобства чтения на маленьких экранах, повторять эту практику в реальном API не надо.

              Начало шикарное. А в реальных системах легкость чтения не важна?

              Вот несколько UUID попробуйте понять есть ли среди них одинаковые:
              13eb9ffc-4e8d-11eb-ae93-0242ac130002
              13eba470-4e8d-11eb-ae93-0242ac130002
              13eba2a4-4e8d-11eb-ae93-0242ac130002
              13eba480-4e8d-11eb-ae93-0242ac130002
              13eba470-4e8d-11eb-ae93-0242ac130002
              13eba538-4e8d-11eb-ae93-0242ac130002

              А теперь предствьте что надо месяцами и годами смотреть в базу где все сущности так идентифицируются.

              Неперебираемые идентификторы нужны только там где потенциально возможен перебор. И даже в этих местах надо оставлять обычный чиловой id и рядом генерить что-то неперебираемое для апи. Тогда и в БД смотреть будет удобно и от перебора зищитимся.
                0

                Тут вопрос в том нужно ли вообще в вашей базе смотреть на много обьектов сразу? Допустим каждая запись это уникальный заказ или что-то в этом духе. Тогда вам достаточно знать что у нее уникальный uuid и это гарантируется индексом. А смотреть на несколько сразу вам все равно не интересно т.к. одна операция выполняется только с одним заказом. В таком случае иметь несколько ид явно плохая идея т.к. появляется возможность ошибиться и например сгенерить для двух разных uuid одинаковые числовые ид.

                  0
                  Опыт эксплуатации разных систем говорит о том что в данные смотреть иногда нужно. С данными всякое непонятное бывает. И приходится лезть в таблички смотреть что там.

                  А смотреть на несколько сразу вам все равно не интересно т.к. одна операция выполняется только с одним заказом.

                  Отчеты самые разнообразные. Сверки любые. Обновления массовые в конце концов. Всегда и везде есть массовые операции.

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

                  числовое — PK
                  uuid — генерировать встроенной в бд функцией при инсерте.

                  Ошибиться везде можно, но в таком сценарии очень сложно.

                  В последнее время я склоняютсь к тому что uuid и подобное вообще не нужно. Случайного лонга хватает для любых практических задач. И человекочитаемость резко повышается. Рейт лимитер на все места публично апи нужен обязательно в любом случае.
                    0

                    Чем случайный long (знаковый?) лучше uuid?

                      0
                      Пусть будет диапазон (1, ulong64 / 2 — 1) для однозначности. Везде удобно и везде влезет.

                      Да, лучше чем uuid. Вероятность коллизий мизерная на практике. Перебрать при любом разумном рейт лимитере невозможно. И смотреть глазами на него удобно.
                      Одни плюсы.
                        0

                        Вы хотели сказать (0, 2^53 — 1) чтобы влезал в JS и прочие языки и платформы, где и целых-то толком нет?

                          0
                          0 я бы исключил, но в общем да. Мы же апи делаем для всех. И не хотим чтобы клиенты страдали просто так.
                  +1
                  Вот несколько UUID попробуйте понять есть ли среди них одинаковые:

                  Во-первых, необходимо использовать рандомные идентификаторы, похожих не будет.
                  Во-вторых, решительно не понимаю, в чем преимущество числовых идентификаторов. Они удобны, пока их мало. Когда счет на миллионы-миллиарды идёт — точно так же поди разбери, в каком знаке отличие.

                    0
                    Во-первых, необходимо использовать рандомные идентификаторы, похожих не будет.

                    Я взял uuid сгенеренные по RFC. Любая хорошая генерирующая функция вам такие же даст. При массовых вставках. Писать самому такие вещи это путь к ошибкам.

                    Во-вторых, решительно не понимаю, в чем преимущество числовых идентификаторов. Они удобны, пока их мало. Когда счет на миллионы-миллиарды идёт — точно так же поди разбери, в каком знаке отличие.

                    Нет же.
                    Цифровые отлично сортируются понятным для человека образом, и отлично сравниваются на глаз.
                    Вот десяток несортированных и достаточно больших
                    108429824
                    516235466
                    671120218
                    719027734
                    240635993
                    161055999
                    494179034
                    180975157
                    105039137
                    945926472


                    А вот они же сортированные
                    105039137
                    108429824
                    161055999
                    180975157
                    240635993
                    494179034
                    516235466
                    671120218
                    719027734
                    945926472


                    Согласитесь гораздо понятнее чем я выше пример с уидами приводил. С уидами сортировка не помогает сделать понятнее.
                      0
                      Я взял uuid сгенеренные по RFC.

                      Согласно какому RFC, если не секрет?

                        0
                        Согласно какому RFC, если не секрет?

                        rfc4122
                          0
                          Set all the other bits to randomly (or pseudo-randomly) chosen values.
                          https://tools.ietf.org/html/rfc4122#page-14

                          Попробуйте взять другой генератор энтропии.

                            0
                            Версия 1. Она чаще встречается на практике.

                            Когда их становится много, это уже не важно. Глаз замыливается.
                            В табличке ключ + несколько ссылок на соседние табличке. И уже даже на экран не влазит. А числа отлично влазят и не мельтешат.
                              0

                              Не очень понимаю, что вы этим хотели сказать.


                              Хороший тон при разработке API — использовать для идентификаторов сущностей глобально уникальные строки, либо семантичные (например, "lungo" для видов напитков), либо случайные (например UUID-4). Это может чрезвычайно пригодиться, если вдруг придётся объединять данные из нескольких источников под одним идентификатором.
                                0
                                Отличать становится проще. А читаемости это не добавляет.
                                Я выше привел пример несложной таблички ключ + допустим 4 ссылки на другие таблички. Это не лезет ни на один экран. Это порождает визуальный шум. Колонки в любом просмотрщике начинают разъежаться.

                                И самое главное вы со мной полностью согласны:
                                в этой книге часто используются короткие идентификаторы типа «123» в примерах — это для удобства чтения на маленьких экранах, повторять эту практику в реальном API не надо.

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

                                  Хинт: если взять последние, ну скажем 6 знаков uuid-а — получится ровно то же самое длинное число. Админки, которые я видел, именно так и делали — последние n знаков показывали, полный uuid за спойлером.

                                    +1
                                    Хорошо когда админка есть, работает и там есть то что нужно…

                                    Мы разработчики и все немного девопсы. Консоль, grep, less, mysql-client, vi. Часто приходится обходится инструментами работаюшиеми всегда и везде.
                                      0

                                      Я, если честно, устал спорить с утверждением «10 десятичных цифр лучше чем 32 шестнадцатеричные». Окей, можете форкнуть репозиторий и исправить в своей копии.

                                        0
                                        Ключевое даже не 10 десятичных.
                                        А использование обычных десятичных чисел по порядку везде где перебор невозможен и где из них нельзя извлечь какую-либо информацию неполучаемую другими путями. В среднем не очень большие цифры будут. Таких мест большинство в среднем приложении. В энтерпрайзе работающем внутри корпоративной сети никакой защиты в этих местах вообще надо.

                                        Обычные числовые PK. Они же id для всего что клиенту отдаем.
                                        А вся защита только в тех местах где она нужна. Как ее делать обсуждаемо на самом деле. С учетом что таких мест не очень много можно и смириться с неудобствами в них.
                                          0

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

                                            0
                                            Обычная sql база. Она всегда внутри любой большой системы сидит.
                                            Без костылей, атомарно, с гарантиями. Все как мы любим.
                                              0

                                              Внутри большой системы обычно несколько баз, и не только sql.


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

                                                0
                                                sql там есть всегда. Ее и используем как генератор id.
                                                Не общий. У каждой сущности свой в своей табличке, там же и саму сущность сохранить можно.
                                                Можно хоть кластер пер тейбл заводить, если за производительность боитесь.

                                                Гарантии это классно. Надо пользоваться. У новых модных систем с ними всегда не очень.
                                                  0

                                                  Есть сущности из нескольких табличек, типа документ с метаданными и табличной частью бьётся на таблички 1:N. С автоид сложно создать его атомарно одним запросом с клиента: сначала надо вставить метаданные в сторонй 1, получить их ид, а потом вставлять строки стороны N с этим id. Ну и необходимость синхронщины вылазит.

                                                    0
                                                    Логически связанные таблички придется делать в одной БД. И тогда все атомарно. Транзакции это классно. И это же поможет атомарные обновления делать. Вин-вин. И никаких распределенных транзакций. Маштабируемся чтением со слейвов, на край шардированием. Далеко не везде нельзя жить без данных за последние секунды.

                                                    При проектировании систем думать надо. Надо делать хорошо, не надо делать плохо. Если сделал плохо надо переделывать.
                        0

                        А зачем сортировать случайные числа?

                          0
                          Это пример некоего набора id в которых мы хотим что-то понять. Одинаковость, чтобы понять должно ли оно джойнится. Просто что там есть чтобы сравнить с тем что в логах нагрепали. Сами придумайте зачем вы смотрите на срез данных из прода. Причин достаточно на самом деле.

                          Это пример насколько удобнее смотреть на числовые id.
                          0
                          Так uuid и есть, по сути, просто определенным способом сгенерированное 128-битное число. Ничего вам не мешает для удобства его отобразить как число в десятичной записи и будет то же самое.
                            0
                            Есть стандарты против которых не попрешь. Отображение uuid из таких. Они хороши для своих случаев.
                            А вот для случая когда можно сделать одну точку генерации (запись в БД например), то есть варинаты удобнее. Подсокращенный случайный лонг, например.
                        +1
                        Я сильно поменял свое мнение после необходимости мерджа БД двух независимых инстансов приложения.
                          0
                          Независимые инстансы БД могут быть только в одном случае. Это шарды. Обычно типовые hash(userId)%N Слияние может быть нетривиально по разным причинам, но как делать понятно.

                          Все остальное это как правило ошибка проектирования. И надо не костыли ставить, а исправлять эту ошибку.
                            0

                            Может быть ситуация, когда одно приложение запущено на нескольких инстансах абсолютно независимо. Общего только кодовая база, и то может отличаться частично. Банально для каждого клиента или для каждого региона присуствия отельный инстанс. А потом прилетает эпик "делаем глобальный SaaS". И если инстаносв много, то мысль о конфликтах айдишнико вознкнет гораздо раньше чем если их только два.

                              0
                              Переименовываем инстансы в шарды.
                              Обучаем приложеньку работать с несколькими шардами.
                              МВП готов. Релизимся.
                              Обучаем жить нескольких клиентов в одном шарде.
                              Придумываем и делаем для всех новых нормальную схему шардирования.
                              Старых переселяем в отдельные схемы, вместо отдельных инстансов и пусть живут как жили. Схем в БД что-ли жалко?

                              Когда-нибудь потом когда будут проблемы придумываем систему решардирования и для новых и для старых. И решардируем все. Это будет через годы, а то и никогда.
                                0

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

                                  0
                                  Тикет на разработку: «Сделать адмнику с возможностью работы со всеми шардами»
                                  С учетом что это админка с никакой нагрузкой и понимающими пользователями можно делать очень неоптимально и не очень красиво.
                                  Выйдет сделать достаточно быстро.
                              0
                              У нас было два независимых региона. Одна кодовая база, но независимые БД в каждом регионе, со своим саппортом и всем остальным. Через несколько лет работы решили один регион дропнуть, а базы объединить. Что тут фиксить?
                                0
                                Зачем его дропать? Вот чем вам мешает вторая схема в БД? У вас шардирование по географическому принципу. Я допускаю что подумав вы решили что оно неоптимально. Если это неоптимально начало стоить много денег надо просто перешардировать по другому признаку.

                                У вас кажется до «такой вариант шардирования слишком дорог и можно съекономить минимум сотню серваков» далеко еще. Так пусть живет. Никому же не мешает.
                                  0

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

                                    0
                                    Например, участились случаи двойных регистраций пользователей и это создаёт проблемы репутации.

                                    Вы вот это серьезно? В мире где сотовый номер стоит рублей 30, а паспортные данные настолько похожие на правду что вы их не проверите рублей 500?

                                    Если у вас Банк или что-то подобное зарегулированное с правом проверки личности, то это делается другими способами. И опять таки с базами и шардированием не связанно никак.

                                    Или регулятор потребовал что у одного юрлица должнен быть только один договор на обслуживание с одним физлицом

                                    Юридические тонкости с техническими решениями не связанны вообще никак.
                                    Отправить к регулятору юристов и пусть разбираются. В худшем случае какую плашку вас заставят повесить или галочку при регистрации добавить.
                                      0

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


                                      А шардирование же вы притащили в тред, нет? Задача была озвучена как слить базы.


                                      Ещё как связаны, особенно с ПДн в последнее время.

                                        0
                                        Я про неудобство пользователей, которые регистрируются на разных сайтах одной компании и потом не могут найти свои покупки, например, и скандалят с саппортом, а то и пишут в спортлото и прочим регуляторам

                                        SSO вроде давно придумали. Зачем вы вообще делаете регистрацию вместо кнопки войти через «Тут все подряд исходя из региональной специфики»?

                                        А шардирование же вы притащили в тред, нет? Задача была озвучена как слить базы.

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

                                        Бизнес пришел с задачей: Мы тут 5 лет назад сделали региональные серваки, а теперь поняли что надо чтобы пользователи с них друг с другом взаимодействовать могли.
                                        Бизнес совсем не хочет слияния баз. Он хочет чтобы пользователи могли взаимодейстовать. И надо подумать как это нормально сделать. В среднем решение сливать базы это худший выбор.

                                        Ещё как связаны, особенно с ПДн в последнее время.

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

                                        Если вы опять таки Банк или кто-то еще кому надо. То такое делается отдельно от всего. Все подсистемы, кроме подсистемы валидации человека, никогда не должны видеть его номер паспорта или пароль. Они просто должны получать ответ — Да это он, Нет это не он.

                                        Проверяющая человека подсистема пишется отдельно, по своим правилам и очень аккуратно.
                                        Тогда и утечек не будет, и о безопасности говорить можно. Если у вас все равномерно везде, то утечка это только вопрос времени.
                                          0
                                          Зачем вы вообще делаете регистрацию вместо кнопки войти через «Тут все подряд исходя из региональной специфики»?

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


                                          Бизнес совсем не хочет слияния баз.

                                          Он хочет именно единую базу — явно озвученное желание. Да, можно в UI/API сделать видимость единой базы, чтоб регион был лишь виртуальным или может даже реальным полем юзера, но непонятно зачем, тем более части проблем это всё равно не решит без существенной переработки основной кодовой базы, чтобы обеспечивала, например, уникальность между двумя идентичными таблицами в соседних схемах.


                                          Вам персональные данные не нужны.

                                          Бизнес дал список нужных им данных. Юристы проредили чтобы не высшую степень защиты обеспечивать, а то хотели и медицинские данные спрашивать чтоб таргетировать околомедицинские товары типа треккеров пульса. Но номера паспортов и ИНН нужны по закону в выбранной бизнес-модели.

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

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

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

                                            У вас все сферическое в вакууме. На практике так не бывает.
                                    0
                                    Я не знаю деталей. В общих чертах стало нерентабельно держать два региона.
                            +1
                            Для пустых результатов поиска часто используют 204 No Data. Это как 404, но без ошибки)
                            Ну и насчёт запрета null это вы зря. Если программист клиента не может отличить null от false, ну так может гнать такого из профессии? А то у вас какой-то детский сад получается, null есть, а слова такого нет.
                              +1
                              Для пустых результатов поиска часто используют 204 No Data. Это как 404, но без ошибки)

                              204 подразумевает пустое тело ответа — а значит, ничего полезного (например, предложение исправить опечатку) в нём не передать. Плюс пустое тело — невалидный json, клиентам придётся обрабатывать его отдельно.


                              Если программист клиента не может отличить null от false, ну так может гнать такого из профессии?

                              Если разработчик API предлагает отличать null от false, может его нужно гнать из профессии?
                              Популярным API чисто статистически будут пользоваться тысячи, может десятки тысяч разработчиков. Они будут допускать ошибки, а результатом этих ошибок будут проблемы реальных людей. Сознательно провоцировать ошибки, мол, нечего джунов нанимать — не очень конструктивная позиция.

                                0

                                Конструктивно использовать нормальные языки типа TypeScript/Scala3/Kotlin в которых нет проблем с null / false, и другие бывают протоколы связи, тот же GraphQL, в котором тоже есть различие между null/false.


                                Но если вы конечно настаиваете на кактусе js/json, то ваша воля

                                  +2

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

                                    –1

                                    Не зависимо от клиента вы можете либо нормальный api, либо убогий api сделать.


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


                                    1. Булево
                                    2. Наличие или отсутствие значения

                                    Вот JavaScript проектировали му*аки, придумали null, undefined
                                    И ещё хуже сделали конструкцию if, которая не различает null, undefined, false, 0… (типа хотели как лучше)


                                    Свободы вы выборе технологий недостижимо, например я захочу на brainfuck/asm/t-sql использовать ваш api, тогда как со свободой выбора?


                                    Дело не в личных желаниях, а в объективной данности

                                    0

                                    Ну и не могу не отметить, что поле, принимающее три значения — true, false и null — это какой-то кадавр, независимо от наличия в языке удобных конструкций для работы с такой псевдотроичной логикой.

                                      0

                                      Кадавр — это если ещё undefined )

                                        0

                                        true, false и null — это optional<bool>

                                      +1
                                      Слушайте, я вас лет 15-20 (виртуально) знаю, поэтому отвечу культурно.
                                      Даже если мы берём элемент управления «чекбокс», почти во всех ОС у него есть «третье состояние» — когда ответ пользователя неизвестен.
                                      И если вебмакака не может передать это третье состояние от сервера интерфейсу — ну…
                                        0

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

                                          0

                                          За что? Удобно же. Например, если нам нужен обязательный ответ на то согласен юзер на спам )) или нет

                                            0

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


                                            Все же, null/optional, выглядят вполне оправданно в определенных моментах.
                                            Это, как мне кажется, также может быть куда прозрачнее, чем связь с дополнительным полем.


                                            А за статью спасибо :)

                                              0

                                              Так вот чтобы никого не оскорблять, должен быть явный пункт «предпочту не говорить» вместо неявного null.

                                                0

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


                                                Т.е. запретить "отсутствие значения" в чекбоксах совсем — выглядит слабо аргументированно.

                                          0
                                          Если разработчик API предлагает отличать null от false, может его нужно гнать из профессии?

                                          Ну это вы вот прямо зря. Погуглите про реляционную алгребру, там это немного используется.
                                          +1
                                          С пустым, но валидным, ответом работать клиенту гораздо удобнее.
                                          Весь код становится чище и однозначнее. Ответ сервера 2xx и ответ успешно десериализовался.
                                          Значит запрос корректный, сервер ответил корректно, ошибок нигде нет. Смело обрабатываем ответ зная что все остальное точно хорошо.
                                          Ошибки идут отдельно. Любой недесериализовавшийся ответ рассматриваем ошибкой.

                                          Любое изменение в этом месте делает больно клиенту. Появляются странные ветки в обработчике ответа. Когда все хорошо, но ответ сервера не десериализуется. И надо это корректно обработать.
                                          0
                                          Вариант 2: разработать формат описания атомарных изменений.

                                          Всё уже украдено разработано до нас: JSON PATCH http://jsonpatch.com/ RFC 6902,

                                            +1

                                            Ну как «разработано». Операции «сдвинь элемент массива на n позиций влево» там нет, например. Если б любая предметная логика описывалась просто правкой JSON-ов, я бы давно помер с голода.

                                              0

                                              Так может слать с клиента на сервер тьюринг-полный байт-код, чтоб он исполнялся там?


                                              Пост в целом в контексте REST воспринимеатся, а это оперирование представлениями, а не RPC

                                                0

                                                Так ровно это REST и предполагает ;) Принцип code-on-demand.
                                                Следующая глава будет про интерфейсы, а вот через неделю планирую взяться за главу ‘Deconstructing the REST myth’

                                            –3
                                            О мои глаза! Зачем я это прочёл?
                                            Молодые люди, именуйте свои статьи не для себя, а для других. Пожалуйста!

                                            Да, я понимаю, что это брюзжание:

                                            А где API-то (API: ru.wikipedia.org/wiki/API )?
                                            О чём вообще приведённые в статье принципы?
                                            Учтите, пожалуйста, поиск по статье слова 'web' не даёт ни одного совпадения! Т.е. это как-бы статья не об (не только об) web-е.

                                            — Используем глобальные идентификаторы?: откроем файл по имени и некоторому guid-у?

                                            — Клиент должен знать состояние системы?: мальчик/девочка, у тебя нет прав чтобы знать полное состояние системы! Даже сама система не знает полного состояния системы. Хотя, технически, можно снять дамп со всех процессов/памяти и др.

                                            — Избегайте не-атомарных операций? Понятно, почему всё так тормозит! Вы бы ещё предложили залочить весь сервер пока обрабатывается каждый запрос! Ну так, на всякий случай.

                                            Чёрт, как же народ-то при разработке POSIX, WinAPI не додумался до уникальных идентификаторов? Вот почему, оказывается, все винду ломают! Не потому, что драйвера не в тех кольцах, или буфера переполняются… а потому что идентификаторов нет.

                                            Ну, и теперь можете накидывать минусов…
                                              +3

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

                                                –1
                                                Апломб? Возможно. Прошу прощения за излишнюю эмоциональность.
                                                Дилетант? Тоже возможно. В том, что Вы описываете — однозначно.
                                                Критика? Да.
                                                Критика чего? Названия статьи!
                                                Попробуйте хотя бы себе ответить: то что Вы написали — это API? Не протокол, формат или др. Именно API?
                                                  +1

                                                  Последний абзац прочитайте, пожалуйста.

                                                    0
                                                    Я прочитал последний абзац. И по ссылке в статье нашёл, что есть API с Вашей точки зрения. И даже оценил изящный переход к JSON-over-HTTP-эндпойтов.

                                                    Ну что сказать? Ок, это Ваша статья и и Ваши определения.
                                                    Тогда удачи Вам в таком нелёгком деле как написание книги.
                                                      0

                                                      Спасибо.

                                              0

                                              Половина из пунктов в статье имеют opinionated и продуманные решения в GraphQL. Так что я бы сказал, что советы наполовину про REST API (со всеми вытекающими ограничениями), а не просто про API.

                                                0

                                                Какие именно советы специфичны для REST и почему?

                                                  0

                                                  Очень много про передачу представлений состояний) Почти ничего про операции, отличные от HTTP методов.

                                                    0

                                                    Состояние в "Representational state" означает состояние, в которое переходит приложение после выбора гиперссылки, и это состояние (равно как и его дальнейшие переходы) определяется представлением ресурса. Тот самый HATEOAS.


                                                    The name "Representational State Transfer" is intended to evoke an image of how a well-designed Web application behaves: a network of web pages (a virtual state-machine), where the user progresses through the application by selecting links (state transitions), resulting in the next page (representing the next state of the application) being transferred to the user and rendered for their use.

                                                    https://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm#sec_6_1


                                                    REST concentrates all of the control state into the representations received in response to interactions. [...] For a browser application, this state corresponds to a "web page," including the primary representation and ancillary representations, such as in-line images, embedded applets, and style sheets.

                                                    https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_3_3

                                                      0

                                                      Ну вот как я понимаю, из-за советов типа Клиент всегда должен знать полное состояние системы пост и воспринимается в контексте REST

                                                        0

                                                        Честно говоря, я не понимаю, что значит знать полное состояние системы ;)

                                                  0

                                                  А можно примеры продуманных решений в GraphQL? Единственное, что он умеет из списка — возвращать пустые массивы без ошибки.

                                                    0

                                                    Может возвращать только нужный стейт, может выполнять только нужные операции, по минимуму переданных данных.

                                                Only users with full accounts can post comments. Log in, please.