Привет, Хабр! Меня зовут Владимир, мне 28 лет и я наркоман наркоман. Мой наркотик – простота. На простоту я подсел из-за своего перфекционизма, которым меня наградили при рождении.

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

Мир вокруг не идеален, сложную вещь сделать простой – невероятно сложно, поэтому всё чрезмерно усложнено. Людям нравиться чувствовать себя профессионалами, поэтому они оперируют сложными терминами, когда в этом нет необходимости, так они ощущают свою значимость и заполняют пустоту, которая образовалась из-за страха потерянного времени.

Усложнить можно всё: ты/Вы/вы
Почему я должен постоянно вспоминать о том, общался я с этим человеком или нет? Мы уже перешли на ты? Я пишу какое-то коммерческое предложение и мне надо «Выкнуть» или это ответ на рядовое письмо и достаточно простого «вы». Почему мне постоянно надо расстраиваться, когда мне с порога совершенно незнакомый человек начинает «тыкать», как будто я его друг с курилки?

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

Так зачем эти сложности?

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

Есть ещё кое-что, что имеет не менее важное значение – время. Время – бесценный ресурс, а выражение «Время — деньги» из уст умных людей вызывает у меня улыбку с разочарованием (выглядишь как идиот и чувствуешь себя так же). Я до сих пор не знаю ни одного миллиардера, которому деньги помогли прожить дольше других людей.

Введение


Речь пойдёт об инструменте, который позволит вам построить полноценный и простой в использовании REST API за минимальное количество времени. Называется он – Python Eve.

К сожалению в Интернете очень много инструкций на эту тему, но все они вводят в заблуждение. Начинающие разработчики, начитавшись подобных статей, думают, что REST API это GET/POST/PUT/DELETE. Заказчики думают, что это дело пары часов. А когда они встречаются вместе, происходят магия в виде Express.js/Mongoose/Passport и ещё кучи хлама, который течёт и временами блокирует event-loop. Всё это запускается с помощью какого-нибудь supervisor, потому что иногда падает и надо как-то перезапускать.

И всё бы ничего, но вчера у меня состоялся разговор с хабра-пользователем, который предложил воспользоваться "Express.js, MongoDB, Mongoose, Passport, отладчиком WebStorm'a и головой на плечах". Похожие разговоры случались часто, поэтому я решил написать эту статью и «отсылать» ссылкой на неё.

Полноценный REST API?


Речь не только о реализации архитектурного стиля REST API, но и о протоколе HTTP, о валидации и кэшировании, о HATEOAS (wikipedia), о котором, похоже, вообще предпочитают не вспоминать. Затем нам понадобится фильтрация результатов и сортировка, постраничная навигация и частичное обновление записей. Потом мы задумаемся о целостности данных и условных запросах. Наверняка нам понадобится аутентификация, возможно захотим отображать данные не только в JSON, но и в XML. Это ещё про версионность и вложенные записи я не упомянул. Затем, как это обычно бывает, какой-то #$%$%^ начнёт долбить в наш могучий API с тяжёлым запросом и нам понадобится ограничить частоту запросов.

Даже если представить, что разработкой такого API займётся невероятно крутой разработчик с 3-мя мониторами, отладчиком WebStorm'a и головой на плечах, он затратит на это не просто много времени, а очень много. Поддержка кодовой базы будет обходиться дорого, а внедрение новых функций будет долгим.



Но мы с вами простоту – любим, а время – уважаем. Так приступим же!

Установка


Это обычный python-пакет, поэтому устанавливается он стандартным способом:

$ pip install eve

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

Быстрый старт


Перед тем, как мы начнём «творить» магию, нам понадобится база данных MongoDB. Если у вас её нет, вы можете воспользоваться любым бесплатным сервисом, например MongoLab. Регистрация займёт не больше минуты. После регистрации создайте базу данных и пользователя для этой базы.

Теперь давайте напишем минимальную версию нашего REST API. Для начала создадим главный файл run.py со следующим содержимым:

from eve import Eve
app = Eve()

if __name__ == '__main__':
    app.run()

Теперь нам надо создать файл настроек settings.py:

# замените user, password, ds049945.mongolab.com, example на ваши данные доступа к БД.
MONGO_URI = "mongodb://user:password@ds049945.mongolab.com:49945/example"

# По умолчанию Eve запускает API в режиме "read-only" (т.е. поддерживаются только GET запросы),
# мы включаем поддержку методов POST, PUT, PATCH, DELETE.
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']

DOMAIN = {
    # Описываем ресурс `/users`
    'users': {
        # Здесь мы описываем модель данных. Для валидации используется модуль Cerberus от автора Eve.
        # Вы можете ознакомиться с ним в официальной документации модуля http://docs.python-cerberus.org/en/stable/.
        # Либо прочитать заметки в официальной документации EVE http://python-eve.org/validation.html#validation.
        'schema': {
            'username': {
                'type': 'string',
                'minlength': 5,
                'maxlength': 32,
                'required': True,
                # уникальное поле (индекс не создаётся, просто значение должно быть уникальным)
                'unique': True,
            },
            'firstname': {
                'type': 'string',
                'minlength': 1,
                'maxlength': 10,
                'required': True,
            },
            'lastname': {
                'type': 'string',
                'minlength': 1,
                'maxlength': 15,
                'required': True,
            },
            'role': {
                
                'type': 'list', # тип: список
                'allowed': ["author", "contributor"], # разрешаем использовать значения: "author", "contributor"
            },
            'location': {
                'type': 'dict', # тип: словарь
                # описываем "схему" словаря
                'schema': {
                    'address': {'type': 'string'},
                    'city': {'type': 'string'}
                },
            },
            'born': {
                'type': 'datetime',
            },
            'active': {
                'type': 'boolean',
                'default': True
            }
        }
    },

    # Описываем ресурс `/groups`
    'groups': {
        # Описываем модель данных (см. выше).
        'schema': {
            'title': {
                'type': 'string',
                'minlength': 5,
                'maxlength': 32,
                'required': True,
                'unique': True
            },
            'users': {
                'type': 'list',  # тип: список
                'default': [],   # по умолчанию: пустой список
                # описываем "схему" списка
                'schema': { 
                    'type': 'objectid', # тип данных: objectid
                    # ссылаемся на запись в другой коллекции
                    'data_relation': {
                        'resource': 'users',  # на ресурс `users` (который мы описали выше)
                        'field': '_id',  # на поле `_id`
                        'embeddable': True
                    }
                }
            }
        }
    }
}

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

Всё готово, запускаем:

$ python3.5 run.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)


Прелюдия


Вы уже подумали, что сейчас мы начнём безбожно "curlить", но я вынужден вас разочаровать. Мы, со свойственным нам перфекционизмом, воспользуемся инструментом автора, который обладает чувством прекрасного:


Инструмент называется HTTPie и ставится в один клик одну команду:

$ pip install httpie

Игрища и забавы


HTTPie вызывается командой "http". Для того, что бы отправить GET запрос к нашему API, выполним:

$ http http://0.0.0.0:5000/
HTTP/1.0 200 OK
Content-Length: 99
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:13:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_links": {
        "child": [
            {
                "href": "users",
                "title": "users"
            },
            {
                "href": "groups",
                "title": "groups"
            }
        ]
    }
}

Благодаря HATEOAS мы видим, что у нас есть 2 ресурса: users и groups. Заглянем внутрь:

» http http://0.0.0.0:5000/users
HTTP/1.0 200 OK
Content-Length: 166
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:20:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 0

{
    "_items": [],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 0
    }
}

Давайте создадим пользователя johndoe:

$ http http://0.0.0.0:5000/users username=johndoe
HTTP/1.0 422 UNPROCESSABLE ENTITY
Content-Length: 184
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:22:44 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_error": {
        "code": 422,
        "message": "Insertion failure: 1 document(s) contain(s) error(s)"
    },
    "_issues": {
        "firstname": "required field",
        "lastname": "required field"
    },
    "_status": "ERR"
}

Первое, на что стоит обратить внимание, это на нашу команду:

$ http http://0.0.0.0:5000/users username=johndoe

HTTPie увидел, что мы отправляем параметр username и превратил его в JSON:

{
    "username": "johndoe"
}

Затем отправил нашему API методом POST. Давайте обратим внимание на ошибки:

"_issues": {
    "firstname": "required field",
    "lastname": "required field"
}

Мы видим сразу весь список ошибок валидации и это здорово. Исправим их и выполним запрос повторно:

$ http http://0.0.0.0:5000/users username=johndoe firstname=John lastname=Doe
HTTP/1.0 201 CREATED
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:34:42 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "24509359443095dd05dece6d0eb7d98cce70b076",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 18:34:41 GMT"
}

Ну вот, мы только что успешно создали нового пользователя. Давайте проверим так ли это:

$ http http://0.0.0.0:5000/users
HTTP/1.0 200 OK
Content-Length: 504
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:36:00 GMT
Last-Modified: Sun, 07 Feb 2016 18:34:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "24509359443095dd05dece6d0eb7d98cce70b076",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_links": {
                "self": {
                    "href": "users/56b78e41cf7b35255aa5a1e6",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 18:34:41 GMT",
            "active": true,
            "firstname": "John",
            "lastname": "Doe",
            "username": "johndoe"
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 1
    }
}

Нет никаких сомнений, что это так. Настало время попробовать перезаписать (обратите внимание, не отредактировать, а перезаписать) пользователя. Делается это с помощью метода PUT, который надо указать явно (если не указать, будет выполнен POST):

$ http put http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 username=janedoe firstname="Jane" lastname="Doe"
HTTP/1.0 403 FORBIDDEN
Content-Length: 101
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:43:04 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_error": {
        "code": 403,
        "message": "An etag must be provided to edit a document"
    },
    "_status": "ERR"
}

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

Для того, что бы мы в такой ситуации не оказались, используется идентификатор ETag. В двух словах — это уникальный идентификатор, который генерируется Eve при каждом изменении записи. Используя этот идентификатор мы можем сказать нашему API, что хотим изменить запись только определённой версии и если она с тех пор была отредактирована, то наши изменения выполнены не будут. Делается это с помощью условного запроса с HTTP заголовком "If-Match":

$ http put http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 "If-Match":"24509359443095dd05dece6d0eb7d98cce70b076" username=janedoe firstname="Jane" lastname="Doe"
HTTP/1.0 200 OK
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:46:56 GMT
ETag: 0138d193174528c205827ba9af25b7b8fb93940e
Last-Modified: Sun, 07 Feb 2016 18:46:56 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "0138d193174528c205827ba9af25b7b8fb93940e",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 18:46:56 GMT"
}

Обратите внимание каким образом мы передали HTTP заголовок HTTPie. Это не единственное, для чего может использоваться идентификатор ETag и условные запросы. Я здесь не буду останавливаться на этом, но советую вам ознакомиться с этой темой, если вы этого ещё не сделали.

Настало время создать новую группу. Для начала попробуем создать группу с несуществующим пользователем:

$ http http://0.0.0.0:5000/groups title="Friends" users:='["56b77466cf7b352414deb451"]'
HTTP/1.0 422 UNPROCESSABLE ENTITY
Content-Length: 220
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:14:31 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_error": {
        "code": 422,
        "message": "Insertion failure: 1 document(s) contain(s) error(s)"
    },
    "_issues": {
        "users": {
            "0": "value '56b77466cf7b352414deb451' must exist in resource 'users', field '_id'."
        }
    },
    "_status": "ERR"
}

И действительно, пользователя с таким _id не существует. Обратите внимание как мы передаём список пользователей:

users:='["56b77466cf7b352414deb451"]'

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

На этот раз мы укажем правильный _id:

» http http://0.0.0.0:5000/groups title="Friends" users:='["56b78e41cf7b35255aa5a1e6"]'
HTTP/1.0 201 CREATED
Content-Length: 278
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:21:42 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 19:21:41 GMT",
    "_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
    "_id": "56b79945cf7b35255aa5a1e7",
    "_links": {
        "self": {
            "href": "groups/56b79945cf7b35255aa5a1e7",
            "title": "Group"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 19:21:41 GMT"
}

«Усё добра», как говорила моя прабабушка. Проверим:

$ http http://0.0.0.0:5000/groups
HTTP/1.0 200 OK
Content-Length: 488
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:24:50 GMT
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 19:21:41 GMT",
            "_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
            "_id": "56b79945cf7b35255aa5a1e7",
            "_links": {
                "self": {
                    "href": "groups/56b79945cf7b35255aa5a1e7",
                    "title": "Group"
                }
            },
            "_updated": "Sun, 07 Feb 2016 19:21:41 GMT",
            "title": "Friends",
            "users": [
                "56b78e41cf7b35255aa5a1e6"
            ]
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "groups",
            "title": "groups"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 1
    }
}

Прабабушка оказалась бы права. На этом можно было бы закончить, но мы поступим иначе.

Получим группу с пользователями, которые в неё входят, в развёрнутом виде:

$ http http://0.0.0.0:5000/groups/56b79945cf7b35255aa5a1e7/\?embedded='{"users":1}'
HTTP/1.0 200 OK
Content-Length: 646
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:38:27 GMT
ETag: c6fc02a0bd4bae92a1310be0748ff8bc971ff209
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 19:21:41 GMT",
    "_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
    "_id": "56b79945cf7b35255aa5a1e7",
    "_links": {
        "collection": {
            "href": "groups",
            "title": "groups"
        },
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "groups/56b79945cf7b35255aa5a1e7",
            "title": "Group"
        }
    },
    "_updated": "Sun, 07 Feb 2016 19:21:41 GMT",
    "title": "Friends",
    "users": [
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "0138d193174528c205827ba9af25b7b8fb93940e",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_updated": "Sun, 07 Feb 2016 18:46:56 GMT",
            "active": true,
            "firstname": "Jane",
            "lastname": "Doe",
            "username": "janedoe"
        }
    ]
}

Попросим тоже самое в XML:

$ http http://0.0.0.0:5000/groups/56b79945cf7b35255aa5a1e7/\?embedded='{"users":1}' "Accept":"application/xml"
HTTP/1.0 200 OK
Content-Length: 690
Content-Type: application/xml; charset=utf-8
Date: Sun, 07 Feb 2016 19:43:36 GMT
ETag: c6fc02a0bd4bae92a1310be0748ff8bc971ff209
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

<resource href="groups/56b79945cf7b35255aa5a1e7" title="Group">
    <link href="groups" rel="collection" title="groups" />
    <link href="/" rel="parent" title="home" />
    <_created>Sun, 07 Feb 2016 19:21:41 GMT</_created>
    <_etag>c6fc02a0bd4bae92a1310be0748ff8bc971ff209</_etag>
    <_id>56b79945cf7b35255aa5a1e7</_id>
    <_updated>Sun, 07 Feb 2016 19:21:41 GMT</_updated>
    <title>Friends</title>
    <users>
        <_created>Sun, 07 Feb 2016 18:34:41 GMT</_created>
        <_etag>0138d193174528c205827ba9af25b7b8fb93940e</_etag>
        <_id>56b78e41cf7b35255aa5a1e6</_id>
        <_updated>Sun, 07 Feb 2016 18:46:56 GMT</_updated>
        <active>True</active>
        <firstname>Jane</firstname>
        <lastname>Doe</lastname>
        <username>janedoe</username>
    </users>
</resource>

Попробуем изменить только имя нашего пользователя (без полной перезаписи). Для этого воспользуемся HTTP методом PATCH:

$ http patch http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 firstname=John "If-Match":"0138d193174528c205827ba9af25b7b8fb93940e"
HTTP/1.0 200 OK
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:46:48 GMT
ETag: 86f3495cf1d6edf301e25563099844bd816c5a3c
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 19:46:47 GMT"
}

Что сказала бы бабуля?

» http http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6
HTTP/1.0 200 OK
Content-Length: 431
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:50:06 GMT
ETag: 86f3495cf1d6edf301e25563099844bd816c5a3c
Last-Modified: Sun, 07 Feb 2016 19:46:47 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "collection": {
            "href": "users",
            "title": "users"
        },
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
    "active": true,
    "firstname": "John",
    "lastname": "Doe",
    "username": "janedoe"
}

«Усё добра». Мне так понравилось, что я бы создавал пользователей пачками:

$ echo '[{"username": "userone", "firstname": "First", "lastname":"Last"},{"username":"usertwo", "firstname":"First", "lastname":"Last"}]' | http http://0.0.0.0:5000/users
HTTP/1.0 201 CREATED
Content-Length: 585
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:01:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "6f397b570ef12769d372c902fa6149bb7e9eaf89",
            "_id": "56b7a29dcf7b35255aa5a1e8",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e8",
                    "title": "User"
                }
            },
            "_status": "OK",
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT"
        },
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "378f30b37724139c213a85079185226ab2b209f3",
            "_id": "56b7a29dcf7b35255aa5a1e9",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e9",
                    "title": "User"
                }
            },
            "_status": "OK",
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT"
        }
    ],
    "_status": "OK"
}

Найдём пользователя "John Doe" по его имени:

$ http http://0.0.0.0:5000/users\?where='{"firstname":"John"}'
HTTP/1.0 200 OK
Content-Length: 535
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:05:28 GMT
Last-Modified: Sun, 07 Feb 2016 19:46:47 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_links": {
                "self": {
                    "href": "users/56b78e41cf7b35255aa5a1e6",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
            "active": true,
            "firstname": "John",
            "lastname": "Doe",
            "username": "janedoe"
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users?where={\"firstname\":\"John\"}",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 1
    }
}

Отсортируем пользователей по их логину в обратном порядке:

$ http http://0.0.0.0:5000/users\?sort\=-username
HTTP/1.0 200 OK
Content-Length: 1203
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:08:08 GMT
Last-Modified: Sun, 07 Feb 2016 20:01:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 3

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "378f30b37724139c213a85079185226ab2b209f3",
            "_id": "56b7a29dcf7b35255aa5a1e9",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e9",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT",
            "active": true,
            "firstname": "First",
            "lastname": "Last",
            "username": "usertwo"
        },
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "6f397b570ef12769d372c902fa6149bb7e9eaf89",
            "_id": "56b7a29dcf7b35255aa5a1e8",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e8",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT",
            "active": true,
            "firstname": "First",
            "lastname": "Last",
            "username": "userone"
        },
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_links": {
                "self": {
                    "href": "users/56b78e41cf7b35255aa5a1e6",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
            "active": true,
            "firstname": "John",
            "lastname": "Doe",
            "username": "janedoe"
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users?sort=-username",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 3
    }
}

Ну и наконец-то удалим всех пользователей:

$ http delete http://0.0.0.0:5000/users
HTTP/1.0 204 NO CONTENT
Content-Length: 0
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:10:06 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

Я бы не стал включать данную возможность в production. Указывается это в параметре RESOURCE_METHODS (стоит убрать из списка DELETE):

RESOURCE_METHODS = ['GET', 'POST', 'DELETE']

Заключение


Мы рассмотрели не все возможности Eve, но даже c учётом этого – получили законченный вариант REST API.

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

Nicola Iarocci, автор Eve, создал прекрасный инструмент для быстрого развёртывания REST API, уделил этому достаточно внимания и сохранил простоту использования.

Ссылки



Подписывайтесь на меня в Twitter, я рассказываю о работе в стартапе, своих ошибках и правильных решениях, о python и всём, что касается веб-разработки.

P.S. Я ищу разработчиков в стартап, подробности у меня в профиле.

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


  1. sim-dev
    08.02.2016 13:29
    -1

    Для гимна краткости пост слегка великоват.

    «Я пишу какое-то коммерческое предложение и мне надо «Выкнуть» или это ответ на рядовое письмо и достаточно простого «вы»» — перфекционист по определению должен в совершенстве знать правила родного языка и пользоваться ими (если Вы не русский человек — вычеркните это предложение).


    1. Tanner
      08.02.2016 13:50
      +7

      «Вы» с заглавной ? это глупость. Перфекционизм требует избавляться от общепринятых глупостей.


      1. sim-dev
        08.02.2016 13:58
        +8

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


      1. reji
        08.02.2016 14:06
        +4

        Слово «Вы» с заглавной несет другой смысл, нежели, чем такое же слово «вы» без заглавной.


        1. Tanner
          08.02.2016 14:19
          +2

          По правилам русского языка местоимение «вы» можно использовать как в обращении к одному лицу, так и к нескольким. Само по себе употребление «вы» вместо «ты» свидетельствует об уважении. В чём тогда выражается этот «другой смысл»?


          1. napa3um
            08.02.2016 18:04
            -2

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


            1. Tanner
              08.02.2016 18:24
              +5

              Это взято из какого-то источника или ваше собственное понимание? В моих источниках другая информация:

              1. «вы» при обращении к одному человеку допустимо по усмотрению автора и не является никаким намёком или маргинализацией,
              2. «Вы» при обращении к нескольким лицам ? банальная орфографическая ошибка.

              Источники:


              1. napa3um
                08.02.2016 18:39

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

                1) Употребление местоимения вы вместо ты при обращении к одному лицу само по себе уже представляет проявление уважительного отношения к этому лицу. Окончательное решение о написании Вы с прописной (для подчеркивания этого уважительного отношения) принимает автор текста.
                2) Таким образом, местоимения Вы, Ваш пишутся с прописной буквы при обращении к одному лицу в текстах следующих жанров: [...] анкеты, рекламные листовки (текст, адресованный неконкретному лицу).


                1. Tanner
                  08.02.2016 19:06

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

                  Скорее всего, мы с вами по-разному поняли слово «смысл» в этом контексте.

                  Я имел в виду смысл, который, например, сообщает слову «всё» две маленькие точечки над буквой «е». Правилами разрешается их не ставить, но смысл слова от этого меняется. А между «вы» и «Вы» такой разницы в смысле не может быть.


                  1. napa3um
                    08.02.2016 19:19

                    > А между «вы» и «Вы» такой разницы в смысле не может быть.

                    Может, и разница в смысле объяснена в вашей первой ссылке и процитирована мною из неё же в ответ. У вас навязчивая идея, вы считаете некоторое правило «логичным», отрицая действительное (зафиксированное лингвистами) положение вещей в языке. Возможно, скоро таких «логиков» накопится критическое число, и правила немного упростят/подкорректируют, чтобы синхронизировать академические источники с реальным опытом носителей языка. Как произошло с кофием и шампунью в своё время, например.


                    1. VolCh
                      09.02.2016 09:51

                      Ну так вполне логично для «логиков» пропагандировать «логичные» варианты как логичные, чтобы быстрее накопилось критическое число употреблений и новая (или хорошо забытая старая) языковая норма сначала стало нормой де-факто, а потом была и зафиксирована «де-юре» в академических словарях.


                      1. napa3um
                        09.02.2016 10:11

                        Кто-то запрещает это делать? Я всего лишь указал на отличие желаемого от действительного. Т.е., «логикам» пока рано говорить, что они уже добились переписывания учебников, т.к. пока не добились. Вне зависимости от моего личного отношения к слову «вы».


                        1. VolCh
                          09.02.2016 10:15

                          Сначала люди начинают использовать новый вариант, а лишь потом переписывают учебники. В целом уже можно, наверное, утверждать, что литературно правильный, со всеми оттенками уважения или неуважения, выбор «вы»/«Вы» используют меньшее число людей, чем неправильный :)


                          1. napa3um
                            09.02.2016 10:24

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


          1. Error_403_Forbidden
            08.02.2016 18:54
            +1

            неверно.
            «вы» надо писать с маленькой буквы за редкими исключениями


      1. mukizu
        08.02.2016 14:09
        +4

        Глупость сказали. Перфекционизм — стремление к совершенству. Все что дальше — сугубо относительно. Что вы выберете эталоном, то и будет.


        1. Tanner
          08.02.2016 14:15

          Нет, совершенствование в заведомо низком искусстве подхалимажа не может быть вызвано перфекционизмом. А иного смысла, кроме подхалимства, «Вы» на письме не имеет.


          1. sim-dev
            08.02.2016 14:18
            +1

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


          1. mukizu
            08.02.2016 14:28
            +1

            >Нет, совершенствование в заведомо низком искусстве подхалимажа не может быть вызвано перфекционизмом

            Можете привести какие-то аргументы в защиту этого утверждения, кроме собственных убеждений? Если нет, то — мимо.

            > А иного смысла, кроме подхалимства, «Вы» на письме не имеет.

            https://ru.wikipedia.org/wiki/%D0%92%D1%8B

            >также употребляется при вежливом или официальном обращении к одному лицу

            >Правила орфографии указывают случаи, когда местоимения «вы», «ваш» (во всех падежах и родах) в середине предложения пишутся с заглавной (прописной) буквы:

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

            >при официальном титуловании оба слова в сочетаниях Ваше (Его, Её) Величество, Ваше (Его, Её) Высочество[4].


            1. Tanner
              08.02.2016 14:54
              -1

              Можете привести какие-то аргументы в защиту этого утверждения


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

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

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


              1. AllexIn
                08.02.2016 15:08
                -1

                мая твая панимать! скора вся наша будет така говорить. проста и панятна. лишняя сложнастя не нужна. процесса уже пошла.


              1. sim-dev
                08.02.2016 15:35
                -1

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


                1. markeev
                  08.02.2016 16:38
                  -1

                  Артемий Лебедев § 165. Три правила про вы
                  www.artlebedev.ru/kovodstvo/sections/165


                1. Tanner
                  08.02.2016 17:18

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

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


              1. ivsedm
                08.02.2016 16:25
                -1

                Без перехода на личности, но все же… ощущение подобных нюансов это результат сложившейся культуры общения у конкретного человека. Он повышается через общение с соответствующем окружением, чтение книг и т.п.
                А что по теме, то для меня различие между «Вы» и «вы» — в степени дистанцированности от человека (знаком я с ним лично или нет; хочу ли я выделить формальность своего обращения), либо желании особо подчеркнуть его статус в моих глазах.


              1. mukizu
                08.02.2016 21:55
                -1

                >Во-первых, с точки зрения логики заглавная буква в «Вы» лишняя: обращение «вы» по сравнению с «ты» и так является уважительным. Масло масляное.

                «вы» с маленькой буквы используется на письме как обращение к множественному лицу. «Вы» с заглавной — к единственному. Банальное разделение для удобства при письме.

                Банальный пример (импровизация на тему письма в контору «Рога и Копыта», письмо адресовано конкретному адресату А):
                «Уведомляю вас, что с первого числа сего месяца, вы должны подавать документ Д не позднее двадцатого числа каждого месяца»

                Кого «вас»? Компанию или адресата?
                Кто «вы»? Лично «он(а)» со своей подписью и паспортом должен явиться в органы или компания через юридический (или какой-то другой) отдел должна подавать документы?

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

                Пример немного утрирован для наглядности.


    1. i360u
      08.02.2016 14:06
      -1

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


    1. Kroid
      08.02.2016 15:00
      -1

      Используя «Вы» в этом комментарии, вы как бы показываете, что вы не перфекционист?


  1. boolive
    08.02.2016 14:10

    Что насчёт CORS (https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)?


    1. vladkozlovski
      08.02.2016 14:45

      Поддерживается «из коробки».


  1. i360u
    08.02.2016 14:12
    +4

    Автор, держи наркоманское пять! Я также всем повторяю, что умение упрощать — один из главных навыков, приобретаемых с опытом и главный абстрактный маркер профессионализма. Тулза — прикольная. Хотелось бы поинтересоваться у знающих людей, есть ли что-то подобное на Go?


    1. youlose
      08.02.2016 14:23
      +2

      Есть отличные плагины для браузеров (тот же postman в google chrome), намного практичнее, удобнее добавлять заголовки, есть история и.т.п.


      1. vladkozlovski
        08.02.2016 15:36
        +2

        Консоль поработила моё сознание уже очень давно. Тут уже ни один плагин не сможет мне помочь.


    1. divan0
      09.02.2016 00:42
      +1

      Есть bat, «inspired by httpie».


  1. f0rk
    08.02.2016 14:57

    С jsonapi.org совместимо?


    1. vladkozlovski
      08.02.2016 15:44

      Нет.


  1. igrishaev
    08.02.2016 15:10
    +5

    Как фреймворк стыкуется с реляционными БД?
    ps: про «вы с маленькой» писать не надо было. Не имеет отношения к теме, только лишний холивар.


    1. vladkozlovski
      08.02.2016 15:23

      Есть поддержка SQLAlchemy с помощью плагина.


  1. Delphinum
    08.02.2016 15:15
    -2

    Я принципиально всем «выкаю»

    Мы с женой разговариваем на «вы», так что я вас понимаю в этом плане )


  1. Lopar
    08.02.2016 18:24
    +1

    Всё написанное до Введения поглотилось на одном дыхании, ибо качественных статей с примерами на тему «упрощай» явно недостаточно.
    И тут, внезапно, программирование. ;)

    Может напишете общую статью?


    1. vladkozlovski
      08.02.2016 18:30

      Часто задумываюсь об этом, но постоянно ловлю себя на мысли – «кто я такой, что бы учить других жизни?».


      1. Lopar
        08.02.2016 20:59
        +2

        Воспримите это как акт обмена опытом. ;)


      1. VolCh
        09.02.2016 09:54
        +1

        Усложняете :) Есть желание — пишите, а не усложняйте себе жизнь размышлениями, не имеющими отношение к желанию :)


  1. vitaly_KF
    08.02.2016 18:55
    +1

    Мне кажется Django REST Framework или Yii2 REST даст гораздо больше возможностей из коробки, так как там помимо чистого API сразу вам будет и админка и миграции базы и всё то, что умеет этот фреймворк + кучу плагинов и большее сообщество.

    Но это имхо. Если же действительно есть БД и нужно лишь апи к ней, то Eve конечно имеет право на существование.


    1. vladkozlovski
      08.02.2016 19:09

      Речь идёт о простоте. Кодовую базу Django я бы простой не назвал. Eve построен на Flask, куча плагинов у него тоже есть и с сообществом нет проблем.

      С ORM тоже непонятно. Если он мне не нужен? Лично я вообще уже забыл когда мне надо было использовать реляционную БД, а вот репликацию и шардинг я использую почти во всех своих проектах.

      Про Yii2 я ничего сказать не могу, PHP руками не трогаю.


      1. vitaly_KF
        08.02.2016 19:36
        +1

        Ок, как обычно всё сводится к «всё зависит от задачи» =)


    1. zolkko
      08.02.2016 22:06

      Из коробки в Django REST Framework'е из всего перечисленного разве что сериализация в json/xml есть.
      Впрочем eve тоже выглядит подозрительно просто.

      Интересно можно ли сделать CQRS на Eve, что бы так же просто было?!


  1. turbo_exe
    08.02.2016 22:24
    +1

    а где контроллеры? где писать бизнес логику? кастомная валидация? что-то сложнее «удалённой db по http» на этом написать можно?


    1. vladkozlovski
      08.02.2016 22:41

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


  1. KIVagant
    08.02.2016 23:28

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


    1. vladkozlovski
      08.02.2016 23:33

      Не только вас. Я даже и не пытался запомнить параметры.

      Рад, что статья вам понравилась).


  1. lostmsu
    09.02.2016 04:41

    Надо в заголовок поста вынести язык/стек или сделать пост языконезависимым.


  1. frol
    09.02.2016 06:57
    +3

    Смотрел я на Python Eve некоторое время назад и знаете, у меня был опыт наступания на подобные грабли — Pinax назывались эти грабли. Всё это дело работает до поры до времени, пока у вас какой-нибудь хитрый случай не вылезет, где простым SELECT/UPDATE не отделаешься и начнётся «сладкая жизнь». Это ещё не говоря о том, что dict с конфигом разрастается в реальном проекте мама не горюй и подстветка синтаксиса такому коду уже не поможет.

    Кстати, интерфейсы взаимодействия в Eve меня пугают — JSON в GET параметрах выглядит странно, HATEOAS тоже не панацея (люди решили почему-то, что обязательно нужно иметь ссылки в API, но API используют машины и они не будут принимать никаких решений на основании содержимого ответа, они могут составить ссылки самостоятельно, в отличие от «кликающего пользователя»).

    P.S. Мои личные поиски «идеального» инструмента завершились на Flask-RESTplus, в котором, пожалуй да, приходится писать чуть больше кода, но зато есть возможность сделать то, что нужно и где нужно. Я в итоге собрал минимальный пример REST API сервера


    1. VolCh
      09.02.2016 10:04

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


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

      А если серьёзно, то HATEOAS — это попытка, как минимум, частично стандартизировать автоматические правила составления ссылок. Самое очевидное применение — одновременно с представлением ресурса получить и список доступных текущему пользователю операций.


      1. frol
        09.02.2016 10:20

        одновременно с представлением ресурса получить и список доступных текущему пользователю операций.
        Ну и что, скажем, мобильное приложение будет с этой информацией по «доступным пользователю действиям» делать? Кнопки дорисовывать автоматически? Вот более развёрнутая мысль — Best Practices for Designing a Pragmatic RESTful API: Should you HATEOAS?


        1. VolCh
          09.02.2016 11:04

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


          1. frol
            09.02.2016 11:19

            Таким образом ответ нельзя закешировать, так как для разных пользователей будут отсутствовать некоторые ссылки, да и даже для одного пользователя нельзя закешировать — вдруг ему права урезали/добавили — инвалидировать кеш запаришься. Да и вообще, как по мне, то мухи должны быть отдельны от котлет. Хотите узнать какие действия можно производить над объектами — спросите об этом отдельно. Объект — это объект, а действия над ним — это отдельная история.


            1. VolCh
              09.02.2016 12:32

              В терминах ООП объект и действия неотделимы :)


              1. frol
                09.02.2016 17:50

                Даже в ООП вы не полезете в данные объекта за информацией о том что разршено делать с объектом, а спросите об этом у модуля безопасности, который, конечно, уже сам может решить проинспектировать запрашиваемый объект. Такой «модуль безопасности» в терминах RESTful API, как мне кажется, принято реализовывать по HTTP-запросу OPTIONS.


    1. vladkozlovski
      09.02.2016 10:31

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

      Они всегда будут принимать решение на основании содержимого ответа. HATEOAS даёт возможность не плодить v1/v2/v3/v1000, для случаев, когда там просто изменился один URL. Можно «хардокить» везде ссылки и пересобирать мобильные приложение и фронтенд каждый раз, если так удобнее.

      Если говорить о каком-нибудь инструменте автоматического генерирования админки, то без HATEOAS там вообще обойтись не получится.

      Кстати, интерфейсы взаимодействия в Eve меня пугают — JSON в GET параметрах выглядит странно

      Не буду отрицать.

      Всё это дело работает до поры до времени, пока у вас какой-нибудь хитрый случай не вылезет, где простым SELECT/UPDATE не отделаешься и начнётся «сладкая жизнь».

      Вы документацию читали? Есть "хуки" на каждое событие, если их их мало, то есть все возможности Flask. Вы же сами дальше пишите, что Flask используете.

      Давно работаю с Eve и такого рода проблем точно не встречал.

      Это ещё не говоря о том, что dict с конфигом разрастается в реальном проекте мама не горюй и подстветка синтаксиса такому коду уже не поможет.

      Есть альтернативные, более короткие, варианты описания моделей? Большой конфиг, разделите его на модели, модели опишите в YAML и всё будет здорово.

      Или вам удобнее модели в Django кодом описывать?


      1. frol
        09.02.2016 10:52

        Они всегда будут принимать решение на основании содержимого ответа. HATEOAS даёт возможность не плодить v1/v2/v3/v1000, для случаев, когда там просто изменился один URL. Можно «хардокить» везде ссылки и пересобирать мобильные приложение и фронтенд каждый раз, если так удобнее.
        Для случая просто смены URL я оставляю старый URL, в документации помечаю deprecated, а в следующей версии, когда других изменений уже накопилось много — удаляю.

        Если говорить о каком-нибудь инструменте автоматического генерирования админки, то без HATEOAS там вообще обойтись не получится.
        По The OpenAPI Specification (fka The Swagger Specification) строить админку куда легче, чем ходить по всем URL.

        Вы документацию читали? Есть «хуки» на каждое событие, если их их мало, то есть все возможности Flask. Вы же сами дальше пишите, что Flask используете.
        Хуки хуками, а бизнес логику куда девать? Её не существует? Самое первое препятствие — роли пользователей, в частности «владелец» объекта и «менеджер», который имеет доступ в том числе ко всем объектам своих подчинённых (в простом случае). Вот так я это реализовал в своём примере — github.com/frol/flask-restplus-server-example/blob/master/app/modules/teams/resources.py#L79

        Или вам удобнее модели в Django кодом описывать?
        Да, почему-то в коде мне их удобнее описывать, могу использовать наследования, например: github.com/frol/flask-restplus-server-example/blob/master/app/modules/teams/schemas.py


        1. vladkozlovski
          09.02.2016 11:24

          Для случая просто смены URL я оставляю старый URL, в документации помечаю deprecated, а в следующей версии, когда других изменений уже накопилось много — удаляю.

          Ну об этом и речь. Я не буду ничего помечать «deprecated» и удалять в следующей версии. Я просто поменяю URL.

          По The OpenAPI Specification (fka The Swagger Specification) строить админку куда легче, чем ходить по всем URL.

          Я не такой любитель стандартов и спецификаций. В начале статьи я написал о простоте, мне чем проще, тем лучше.

          Я 2.5 года работаю в стартапе без выходных, каждый день по 12-14 часов, иногда и того больше. Работы с каждым днём меньше не становится и если бы я каждый раз тратил на задачу больше времени, чем требуется, хотя бы на чуть-чуть, то стартап закрылся бы уже давно, а я лежал в больнице с переутомлением.

          Хуки хуками, а бизнес логику куда девать? Её не существует? Самое первое препятствие — роли пользователей, в частности «владелец» объекта и «менеджер», который имеет доступ в том числе ко всем объектам своих подчинённых (в простом случае).

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


  1. frol
    09.02.2016 10:24
    +1

    Да, ещё меня в Python Eve удивила реализация PATCH. Please. Don't Patch Like An Idiot. — William Durand. Если кратко, то есть RFC 6902, а в Eve решили придумать свой велосипед. (Кстати, в моём примере RESTful API сервера, по ссылке выше, PATCH реализован как рекомендует RFC)


    1. vladkozlovski
      09.02.2016 11:08
      -1

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

      [
          { "op": "test", "path": "/a/b/c", "value": "foo" },
          { "op": "remove", "path": "/a/b/c" },
          { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
          { "op": "replace", "path": "/a/b/c", "value": 42 },
          { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
          { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
      ]
      

      Так они хотят вносить изменения в JSON. Для XML предлагают вообще XML Path использовать. Но мне надо просто изменить значение, я не хочу ничего знать об XML Patch. Завтра вам понадобится использовать какой-нибудь другой формат данных, например YAML и надо будет придумать свой велосипед. Это если закрыть глаза на то, что составление такого запроса на порядок сложнее и не упростит реализацию мобильного клиента. А парсинг таких запросов на сервере, их валидация и обработка буду занимать больше времени.

      У меня был опыт разработки WebDAV, в котором используется много стандартов. Также имел опыт создания CalDAV и CardDAV, которые являются дополнениями к WebDAV и могу вам сказать следующее: никто и никогда полностью не соблюдает стандарты, в особенности, когда они сложны. Реализация более-менее функционального сервиса WebDAV превращается в большую кучу патчей для всех распространённых клиентов.

      Если вы попробуете использовать штатный WebDAV клиент Windows/OS X, то вряд ли найдёте хотя бы несколько серверов, с которыми у вас не будет проблем. Про CalDAV/CardDAV клиентов я промолчу. А ведь всё описано стандартами.

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

      Стандарты это круто, пока следуют им другие.


      1. frol
        09.02.2016 11:39
        +1

        Сложно? Да, чуть сложнее, чем решение в лоб, зато расширяемо. Не нравится RFC — напишите свою, но реализация «мне так нравится» подходит для своего личного пользования, а для фреймворка — это минус. OpenAPI (Swagger) спецификацией можно описать любой каприз и после этого у вас будет и интерактивная документация, и автогенерируемые клиенты на куче языков, но Eve не пошёл путём интеграции с Swagger, так что да, написать сервер просто, но потом клиента к нему писать придётся самому и работать с особенностями Eve на клиенте — тоже.

        Опять же, можете у меня в примере посмотреть и как это выглядит в Swagger документации (Docker образ поднимается за полминуты), и в коде (я пока не оформил этот случай в отдельную обёртку, но думаю об этом), и в:

        OpenAPI (Swagger) Specification JSON, который автоматически собирается Flask-RESTplus
        "patch": {
        	"description": "**PERMISSIONS: Owner/Supervisor/Admin may execute this action.**",
        	"operationId": "patch_team_by_id",
        	"parameters": [
        		{
        			"in": "path",
        			"name": "team_id",
        			"required": true,
        			"type": "integer"
        		},
        		{
        			"in": "body",
        			"name": "body",
        			"required": true,
        			"schema": {
        				"items": {
        					"properties": {
        						"op": {
        							"enum": [
        								"add",
        								"test",
        								"remove",
        								"replace"
        							],
        							"location": "json",
        							"type": "string"
        						},
        						"path": {
        							"enum": [
        								"/title"
        							],
        							"location": "json",
        							"type": "string"
        						},
        						"value": {
        							"location": "json",
        							"type": "string"
        						}
        					},
        					"required": [
        						"op",
        						"path"
        					]
        				},
        				"location": "json",
        				"type": "array"
        			}
        		}
        	],
        	"responses": {
        		"200": {
        			"description": "Success",
        			"schema": {
        				"$ref": "#/definitions/DetailedTeamSchema"
        			}
        		},
        		"401": {
        			"description": "Authentication with teams:write scope(s) is required",
        			"schema": {
        				"$ref": "#/definitions/401HTTPErrorSchema"
        			}
        		},
        		"403": {
        			"description": "Owner/Supervisor/Admin may execute this action.",
        			"schema": {
        				"$ref": "#/definitions/403HTTPErrorSchema"
        			}
        		},
        		"404": {
        			"description": "Team not found.",
        			"schema": {
        				"$ref": "#/definitions/404HTTPErrorSchema"
        			}
        		},
        		"409": {
        			"description": "A conflict happened while processing the request.  The resource might have been modified while the request was being processed.",
        			"schema": {
        				"$ref": "#/definitions/409HTTPErrorSchema"
        			}
        		},
        		"422": {
        			"description": "The request was well-formed but was unable to be followed due to semantic errors.",
        			"schema": {
        				"$ref": "#/definitions/422HTTPErrorSchema"
        			}
        		}
        	},
        	"security": [
        		{
        			"oauth2": [
        				"teams:write"
        			]
        		}
        	],
        	"summary": "Patch team details by ID",
        	"tags": [
        		"teams"
        	]
        }
        


        1. vladkozlovski
          09.02.2016 11:47
          -1

          Я не буду рассуждать на тему: правильно/неправильно. Я не разработчик фреймворка и не учавствовал в создании стандартов. Я выражаю лишь своё мнение — я считаю вариант Eve удобнее. Более того, вариант Eve используется в большинстве случаев.

          но потом клиента к нему писать придётся самому и работать с особенностями Eve на клиенте — тоже

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


          1. frol
            09.02.2016 12:07

            Для клиента из OpenAPI (Swagger) Specification (официальный пример PetStore API) генерируется ООП модуль/библиотека (вот что генерируется автоматически для Python), так что на клиенте даже не нужно возиться с URL, парсингом или другим, а просто сразу приступаешь к работе с объектами, что-то в духе:

            from petstore_swagger_api import PetApi
            
            pet_api = PetApi()
            available_pets = pet_api.find_pets_by_status(status='available')