Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Автоматизируем синхронизацию HTTP API и документации

Автоматизируем синхронизацию HTTP API и документации

Dmitry Efimov

October 01, 2020
Tweet

More Decks by Dmitry Efimov

Other Decks in Programming

Transcript

  1. Дмитрий Ефимов Работаю бэкенд-разработчиком в FunBox. Девлид Ruby-команды. Пишу веб

    на Ruby более 5 лет. ВЕЩАЕТ СВЯЗАТЬСЯ СО МНОЙ vk.com/tuwilof @tuwilof facebook.com/tuwilof instagram.com/tuwilof 3 github.com/tuwilof twitter.com/tuwilof
  2. FunBox Более 13 лет мы разрабатываем решения для сотовых операторов

    на Ruby, Ruby on Rails, Erlang, Elixir, Python. О КОМПАНИИ МЫ В СОЦСЕТЯХ twitter.com/funbox_team youtube.com/funboxteam 4 github.com/funbox instagram.com/funboxteam facebook.com/funboxteam funbox.ru
  3. apipie-rails 11 class Api::SessionsController < Api::ApplicationController resource_description do name "Аутентификация"

    end api! 'Вход в систему' param: login, String, required: true, desc: "Логин пользователя" param: password, String, required: true, desc: "Пароль пользователя" error 401, "Ошибка в логине или пароле" example 'можно написать пример тела запроса или ответа' def create … end end
  4. Плюсы, но ещё и минусы + Комментарии + Дока рядом

    с кодом 13 – Комментарии – Ручное дублирование
  5. rspec_api_documentation 15 require 'rails_helper' require 'rspec_api_documentation/dsl' resource "Orders" do get

    "/orders" do example "Listing orders" do do_request expect(status).to eq 200 end end end
  6. Плюсы, но ещё и минусы + Не надо писать документацию

    + В документации то же самое, что и в тестах 17 – Надо начинать с тестов – Много JSON’ов
  7. 23

  8. 24

  9. Плюсы, но ещё и минусы + Не REST + Не

    надо писать валидацию 26 – Не REST – Сложнее оптимизация – Выше порог поддержки – Выше порог тестирования – Всё равно нужно тащить сгенерированный клиент
  10. Документация >= Код – Hyper-Schema – OpenAPI 2 и OpenAPI

    3 (Swagger) – API Blueprint и другие 31
  11. [ { "path": "/sessions", "method": "POST", "content-type": "application/json", "requests": [query

    string and body X], "responses": [ { "status": "401", "content-type": "application/json", "body": {} }, { "status": "429", "content-type": "application/json", "body": {} }, { "status": "201", "content-type": "application/json", "body": X } ] } ] 33
  12. [ { "path": "/sessions", "method": "POST", "content-type": "application/json", "requests": [query

    string and body X], "responses": [ { "status": "401", "content-type": "application/json", "body": {} }, { "status": "429", "content-type": "application/json", "body": {} }, { "status": "201", "content-type": "application/json", "body": X } ] } ] 34
  13. [ { "path": "/sessions", "method": "POST", "content-type": "application/json", "requests": [query

    string and body X], "responses": [ { "status": "401", "content-type": "application/json", "body": {} }, { "status": "429", "content-type": "application/json", "body": {} }, { "status": "201", "content-type": "application/json", "body": X } ] } ] 35
  14. [ { "path": "/sessions", "method": "POST", "content-type": "application/json", "requests": [query

    string and body X], "responses": [ { "status": "401", "content-type": "application/json", "body": {} }, { "status": "429", "content-type": "application/json", "body": {} }, { "status": "201", "content-type": "application/json", "body": X } ] } ] 36
  15. [ { "path": "/sessions", "method": "POST", "content-type": "application/json", "requests": [query

    string and body X], "responses": [ { "status": "401", "content-type": "application/json", "body": {} }, { "status": "429", "content-type": "application/json", "body": {} }, { "status": "201", "content-type": "application/json", "body": X } ] } ] 37
  16. 38

  17. JSON { "kind": "book" }, { "kind": "magazine" } JSON-SCHEMA

    { "properties": { "kind": { "type": "string", "enum": [ "book", "magazine"] } } "required": ["kind"] } 40
  18. class YourMiddleware def initialize(app) @app = app end def call(env)

    status, headers, body = @app.call(env) [status, headers, body] rescue Esplanade::Request::NotDocumented [ 200, {'Content-Type' => 'application/json; charset=utf-8'}, [{status: :incorrectRequest, error: :requestNotDocumented}.to_json] ] rescue Esplanade::Request::BodyIsNotJson [ 200, {'Content-Type' => 'application/json; charset=utf-8'}, [{status: :incorrectRequest, error: :requestBodyIsNotJson}.to_json] ] rescue Esplanade::Request::Invalid => e [ 200, {'Content-Type' => 'application/json; charset=utf-8'}, [{status: :incorrectRequest, error: :requestBodylsInvalid, details: e}.to_json] ] end end 45
  19. Fully conforming requests: DELETE /api/v1/book 200 201 404 DELETE /api/v1/book/{id}

    200 201 404 GET /api/v1/book/{id}/seller 200 201 404 Partially conforming requests: GET /api/v1/book 200 404 POST /api/v1/book 200 201 404 GET /api/v1/book/{id} 200 404 200 PATCH /api/v1/book/{id} 200 201 404 Non-conforming requests: GET /api/v1/seller 200 201 404 GET /api/v1/buyer 200 404 API requests with fully implemented responses: 3 (33.33% of 9). API requests with partially implemented responses: 4 (44.44% of 9). API requests with no implemented responses: 2 (22.22% of 9). API responses conforming to the blueprint: 16 (64.00% of 25). API responses with validation errors or untested: 9 (36.00% of 25). 51
  20. [ { "method": "GET", "path": "/api/v1/book", "body": {}, "response": {

    "status": 200, "body": { "title": "The Martian Chronicles" } }, "title": "/spec/controllers/api/v1/books_controller_spec.rb:11", "group": "/spec/controllers/api/v1/books_controller_spec.rb" }, { "method": "POST", "path": "/api/v1/book", "body": {}, "response": { "status": 200, "body": { "title": "The Old Man and the Sea" } }, "title": "/spec/controllers/api/v1/books_controller_spec.rb:22", "group": "/spec/controllers/api/v1/books_controller_spec.rb" } ] 52
  21. 54

  22. 56

  23. 58

  24. 62

  25. Выводы – Подходов много, рассмотри все – API и документацию

    можно синхронизировать без генерации 64