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

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

Avito.Tech Background

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

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

Граммар-наци

JSONAPI
JSON API
JSON-API
JSON:API

Граммар-наци

JSONAPI
JSON API
JSON-API
JSON:API
jsonapi-pr
jsonapi-site

A SPECIFICATION FOR BUILDING APIS IN JSON

morti

Junior

blogexample

Интерфейс API

  1. Создать статью

Интерфейс

Метод Путь Действие
GET /createArticle Создать статью
GET /articlesList Получить список статей
GET /articles/getById?id=1 Получить статью
GET /articles/update?newTitle=Заголовок Обновить статью
LOL /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
            <ПУСТАЯ СТРОКА>
        

MIME-типы

mime types

Коды ответов

200 OK Cat

Что можно было

451 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" } }
              }, ...
            }]}
        

👌 А ещё можно вот так

            GET /articles?include=author HTTP/1.1
        
            GET /articles?include=author,image HTTP/1.1
        
            GET /articles?include=author,author.avatar HTTP/1.1
        

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

Выведем 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?fields[article]=title HTTP/1.1
        
            HTTP/1.1 200 OK
            { "data": [{
                "id": "1", "type": "article",
                "attributes": { "title": "Про JSON:API" },
              }, ... ]
            }
        

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

👌 Решение

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

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

👌 Решение

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

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

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

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

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

              ...
              "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
http-rest-jsonapi

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

Список реализаций спефикации — https://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" }, ...]
                },
              }
        

OpenAPI (Swagger)

Petstore
Petstore Model
JSON:API Swagger

Чем JSON:API отличается
от GraphQL?

ВСЕМ

Спецификация
vs
Язык запросов
REST
vs
не REST
Эволюция
vs
Революция

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

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',
                ...
        

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

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

👏 Спасибо!

я