JSON API — работаем по спецификации

Алексей Авдеев, Neuron.Digital

JSON API — работаем по спецификации

Алексей Авдеев, Neuron.Digital

FrontendConf Background

👨‍💻 О себе

  1. 👨 Алексей Авдеев (https://github.com/avdeev)
bellefonte-nuclear-power-plant

Закон тривиальности Паркинсона

Время, потраченное на обсуждение пункта, обратно пропорционально рассматриваемой сумме

bellefonte-nuclear-power-plant

The Bikeshed Effect

sleep(1)

It was a proposal to make sleep(1) DTRT if given a non-integer argument that set this particular grass-fire off. I'm not going to say anymore about it than that, because it is a much smaller item than one would expect from the length of the thread, and it has already received far more attention than some of the *problems* we have around here.

Poul-Henning Kamp <[email protected]>

http://phk.freebsd.dk/sagas/bikeshed.html

Количество шума, создаваемого изменением, обратно пропорционально сложности изменения

Что важнее?

Формат API
или
Бизнес задачи

API - велосипедный сарай

Как избежать

Anti-bikeshedding tool

Начинаем проектировать

bellefonte-nuclear-power-plant

REST (2000)

Требования к архитектуре REST

RESTful-блог

blogexample
junior developer
CRUD

Интерфейс

Интерфейс

Метод Путь Действие
GET /createArticle Создать статью
GET /articlesList Получить список статей
GET /articles/getById?id=1 Получить статью
GET /articles/update?newTitle=Заголовок Обновить статью
GET /deleteArticle?id=1 Удалить статью

👌 Правильно

Метод Путь Действие
POST /articles Создать статью
GET /articles Получить список статей
GET /articles/1 Получить статью
PATCH /articles/1 Обновить статью
DELETE /articles/1 Удалить статью

Почему так правильно?

Удаляем статью

            DELETE /articles/1 HTTP/1.1
            Accept: application/json
        
            HTTP/1.1 200 OK
            Content-Type: application/json
            null
        

MIME-типы

mime types

Коды ответов

200 OK Cat

Создание сущности

Создание сущности

            POST /articles HTTP/1.1
            Content-Type: application/json
            { "id": 1, "title": "Про JSON API"}
        
            HTTP/1.1 422 Unprocessable Entity
            HTTP/1.1 403 Forbidden
            HTTP/1.1 500 Internal Server Error
        

Возвращайте ошибки

👌 После

            HTTP/1.1 422 Unprocessable Entity
            Content-Type: application/json
             
            { "errors": [{
                "status": "422",
                "title":  "Title already exist",
            }]}
        

Добавим паджинацию

Паджинация

Запрос списка

            GET /articles HTTP/1.1
            Content-Type: application/json
        
            HTTP/1.1 200 OK
            [
              { "id": 1, "title": "Про JSON API"},
              { "id": 2, "title": "Про XML-RPC"}
            ]
        

👌 После

            GET /articles?page[size]=30&page[number]=2
            Content-Type: application/json
        
            HTTP/1.1 200 OK
            {
              "data": [{ "id": 1, "title": "JSON API"}, ...],
              "meta": { "count": 10080 }
            }
        

👌 Или так

            GET /articles?page[offset]=30&page[limit]=30
            Content-Type: application/json
        
            HTTP/1.1 200 OK
            {
              "data": [{ "id": 1, "title": "JSON API"}, ...],
              "meta": { "count": 10080 }
            }
        

👌 Или так

            GET /articles?page[published_at]=1538332156
            Content-Type: application/json
        
            HTTP/1.1 200 OK
            {
              "data": [{ "id": 1, "title": "JSON API"}, ...],
              "meta": { "count": 10080 }
            }
        

Проблема N + 1

Выведем 10 статей с указанием автора

Итого: 11 запросов

Добавляем связи

Запрос списка со связями

            GET /articles?include=author
            Content-Type: application/json
        

👌 Решение: запрос списка со связями

            HTTP/1.1 200 OK
            { "data": [{
              { attributes: { "id": 1, "title": "JSON API" },
              { relationships: {
                "author": { "id": 1, "name": "Avdeev" } }
              }, ...
            }]}
        

Проблема дублирования данных

Выведем 10 статей с указанием автора, у всех статей один автор

Итого: один автор включен в ответ 10 раз

👌 Решение: нормализация данных

            HTTP/1.1 200 OK
            { "data": [{
                "id": "1", "type": "article",
                "attributes": { "title": "JSON API" },
                "relationships": { ... }
              }, ... ]
            }
        

👌 Решение: нормализация данных

            HTTP/1.1 200 OK
            { "data": [{
                ...
                "relationships": {
                  "author": { "id": 1, "type": "people" } }
                }
              }, ... ]
            }
        

👌 Решение: нормализация данных

            HTTP/1.1 200 OK
            {
              "data": [ ... ],
              "included": [{
                "id": 1, "type": "people",
                "attributes": { "name": "Avdeev" }
              }]
            }
        

Нужны не все поля ресурса

👌 Решение

            GET /articles/1?fields[article]=title HTTP/1.1
        
            HTTP/1.1 200 OK
            { "data": [{
                "id": "1", "type": "article",
                "attributes": { "title": "Про JSON API" },
              }, ... ]
            }
        

Поиск по статьям

👌 Решение

            GET /articles/1?filters[search]=api HTTP/1.1
        
            GET /articles/1?filters[from_date]=1538332156 HTTP/1.1
        
            GET /articles/1?filters[is_published]=true HTTP/1.1
        
            GET /articles/1?filters[author]=1 HTTP/1.1
        

Нужна
сортировка

👌 Решение

            GET /articles/1?sort=title HTTP/1.1
        
            GET /articles/1?sort=published_at HTTP/1.1
        
            GET /articles/1?sort=-published_at HTTP/1.1
        
            GET /articles/1?sort=author,-published_at HTTP/1.1
        

Нужно поменять URLs

👌 Решение: гипермедиа

            GET /articles HTTP/1.1
            {
              "data": [{
                  ...
                  "links": { "self": "http://localhost/articles/1" },
                  "relationships": { ... }
              }],
              "links": { "self": "http://localhost/articles" }
            }
        

👌 Решение: гипермедиа

              ...
              "relationships": {
                "comments": {
                  "links": {
                    "self": "http://localhost/articles/1/relationships/comments",
                    "related": "http://localhost/articles/1/comments"
                  }
                }
              }
        

Новый Media Type

(registered 2013-07-21, last updated 2013-07-21)
Name : Steve Klabnik
Email : steve&steveklabnik.com
MIME media type name : Application
MIME subtype name : Vendor Tree - vnd.api+json

http://www.iana.org/assignments/media-types/application/vnd.api+json
JSON API

Экосистема JSON API

Список реализаций спефикации — http://jsonapi.org/implementations/

➕ Плюсы JSON API

🤯 Минусы JSON API

Подводные
камни JSON API

🤔 Количество relationships в выдаче неограничено

            GET /articles/1?include=comments HTTP/1.1
        
              ...
              "relationships": {
                "comments": {
                  "data": [0 ... ∞]
                }
              }
        

☝️ Правильно

            GET /comments?filters[article]=1&page[size]=30 HTTP/1.1
        
            {
              "data": [0 ... 29]
            }
        

Неоднозначность

            GET /articles/1?include=comments HTTP/1.1
        
            GET /articles/1/comments HTTP/1.1
        
            GET /comments?filters[article]=1 HTTP/1.1
        

🤔 Полиморфные связи "один ко многим"

            GET /comments?include=commentable HTTP/1.1
             
              ...
              "relationships": {
                "commentable": {
                  "data": { "type": "article", "id": "1" }
                }
              }
        

🤔 Сложные связи "многие ко многим"

            GET /users?include=users_comments HTTP/1.1
             
              ...
              "relationships": {
                "users_comments": {
                  "data": [{ "type": "users_comments", "id": "1" }, ...]
                },
              }
        

Swagger

Petstore
Petstore Model
JSON API Swagger

Альтернативы

OData (2015)

OData (2015) - the best way to REST

            GET http://services.odata.org/v4/TripRW/People HTTP/1.1
            OData-Version: 4.0
            OData-MaxVersion: 4.0
        

OData (2015) - the best way to REST

            HTTP/1.1 200 OK
            Content-Type: application/json; odata.metadata=minimal
            OData-Version: 4.0
            {
              '@odata.context': 'http://services.odata.org/V4/...
              '@odata.nextLink': 'http://services.odata.org/V4/...
              'value': [{
                '@odata.etag': 'W/'08D1D5BD423E5158'',
                'UserName': 'russellwhyte',
                ...
        

Что с GraphQL?

Высокий порог входа

стримерша

💥 Эффект большого взрыва

Или

🤯 Ад на бэкенде

За подробностями

  1. http://jsonapi.org
  2. http://www.odata.org
  3. https://graphql.org
  4. http://xmlrpc.scripting.com
  5. https://www.jsonrpc.org
bike-shedding