Самодокументированные микросервисы (ArangoDB + swagger)

habr.png

Поддержание документации к микросервисам в актуальном состоянии по прежнему требует предельной дисциплины при разработке, ну и больших трудозарат. Очень разумный подход к созданию документации предлагает, например, GraphQL, где документация неразрывно связана с программным кодом и этим гарантируется 100% соответствие документации и документируемых сервисов. Однако, непривычность подхода GraphQL для разработчиков, привыкших к REST-API, все еще затрудняет продвижение этой технологии в практическую разработку приложений. Тут же можно вспомнить и SOAP, который уже давно решил проблему соответствия документации и сервисов, но из-за переусложненности не прижился в широких массах разработчиков.

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

ArangoDB — гибридная, документо+граф-ориентированная база данных.

Foxx — микросервисный фреймверк, встроенный в базу данных ArangoDB. Работает на движке JavaScript, который (в отличие от nodejs) может одновременно выполняеться в неограниченном количестве параллельных потоков (не блокирующих друг друга), вследствие чего нет необходимости в конструкциях promise/than/canch и async/await. В отличие от mongodb, в которой не рекомендуется злоупотреблять серверными процедурами, и реляционных баз данных, в которых хранимые процедуры исползуются также осторожно и уж точно не взаимодейтсвуют по REST-API с клиентами (браузерами, мобильными приожениями и т.п.) — микросервисный фреймверк Foxx был разработан как раз для разработки микросервисов которые напрямую общаются по http-протоколу с клиентами.

Swagger — это программная среда с открытым исходным кодом, поддерживаемая большой экосистемой инструментов, которая помогает разработчикам разрабатывать, создавать, документировать и потреблять веб-службы RESTful. Хотя большинство пользователей идентифицируют Swagger с Swagger UI, набор инструментов Swagger включает поддержку автоматической документации, генерации кода и генерации тестовых примеров.

То, что Swagger включает подержку генерации кода это ситуация противовположная той, которую хотелось бы получить — когда код поддерживает генерацию документации. То что предлагает нам ArangoDB+Foxx как раз включает обратный вариант. Когда по коду микросервисов генерируется схема для Swagger-а. Впрочем, теперь в этом можно убедиться самостоятельно проделав минимум работы.

У Вас должна быть установлена ArangoDB, чтобы выпонить дальнейшие действия.

  1. Входим в админку и выбираем пункт создания нового микросервиса Services→Add service→New.
  2. Заполняем в открывшейся форме обязательные реквизиты. В поле «Document Collections» добавляем имя коллекции документов которая будет создана при разворачивании микросервиса. Например, cats.
  3. Кликаем по кнопке install, заносим в поле url, к которому будет примонирован микросервис.


При инсталяции микросервиса, для каждой коллекции из поля «Document Collections» (см. п.2) будут созданы роуты, реализующие CRUD операции методами POST, GET, PUT и DELETE. Впрочем, это только заготовки методов, которые можно менять, удалять, добавлять новые. Мы выбрали одну коллекцию при содании микросервиса (cats), хотя могли этого и не деать, а все добавить потом вручную.

Тепрь у нашей коллекции cats есть роуты для CRUD опрераций, и мы можем начать вызывать эти роуты из админки базы данных выбрав вкладку API (Services→[Имя микросервиса]→API). В этом вкладке расположен привычный интерфейс Swagger-а. Также есть возможность опубиковать интерфейс Swagger-а на внешнем роуте доступном не только через админку, но как обычный URL.

Если попробовать добавить документ в коллекцию cats методом POST {«name»: «Tom»}, то получим статус с ошибкой. Т.к. поле name мы нигде еще не определили. Поэтому продолжим дальше ирабоу с админкой ArangoDB.

4. Для более удобной разработки в ArangoDB предусмотрен режим Development, который включается на вкладке Settings (Services→[Имя микросервиса]→Settings-Set development)

Теперь можно менять код миросервиса и сразу наблюдать результат (без дополнительного деплоя). Каталог, в котором расплолжен программный код микросервиса можно узнать в админке на вкладке Info (Services→[Имя микросервиса]→Info).

Посмотрим как выглядит определние роута POST

'use strict';
const dd = require('dedent');
const joi = require('joi');
const httpError = require('http-errors');
const status = require('statuses');
const errors = require('@arangodb').errors;
const createRouter = require('@arangodb/foxx/router');
const Cat = require('../models/cat');

const cats = module.context.collection('cats');
const keySchema = joi.string().required()
.description('The key of the cat');

const ARANGO_NOT_FOUND = errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code;
const ARANGO_DUPLICATE = errors.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED.code;
const ARANGO_CONFLICT = errors.ERROR_ARANGO_CONFLICT.code;
const HTTP_NOT_FOUND = status('not found');
const HTTP_CONFLICT = status('conflict');

const router = createRouter();
module.exports = router;

router.tag('cat');

router.post(function (req, res) {
  const cat = req.body;
  let meta;
  try {
    meta = cats.save(cat);
  } catch (e) {
    if (e.isArangoError && e.errorNum === ARANGO_DUPLICATE) {
      throw httpError(HTTP_CONFLICT, e.message);
    }
    throw e;
  }
  Object.assign(cat, meta);
  res.status(201);
  res.set('location', req.makeAbsolute(
    req.reverse('detail', {key: cat._key})
  ));
  res.send(cat);
}, 'create')
.body(Cat, 'The cat to create.')
.response(201, Cat, 'The created cat.')
.error(HTTP_CONFLICT, 'The cat already exists.')
.summary('Create a new cat')
.description(dd`
  Creates a new cat from the request body and
  returns the saved document.
`);


Как валидация так и документирование, основаны на использовании схемы объекта. Внесем в нее небольшие изменения, доабавив поле name:

'use strict';
const _ = require('lodash');
const joi = require('joi');

module.exports = {
  schema: {
    // Describe the attributes with joi here
    _key: joi.string(),
    name: joi.string().description('cat`s name'), // добавили новое поле
  },
  forClient(obj) {
    // Implement outgoing transformations here
    obj = _.omit(obj, ['_id', '_rev', '_oldRev']);
    return obj;
  },
  fromClient(obj) {
    // Implement incoming transformations here
    return obj;
  }
};


Перейдя в закадку API, можно убедиться, что схема поменялась и теперь объект с полем name можно добавить в коллекцию cats.

apapacy@gmail.com
12 нобря 2018 года.

© Habrahabr.ru