Как стать автором
Обновить

Комментарии 30

Насколько я знаю, соединение с монгой может быть утеряно. То, как сделано сейчас (единожды коннектимся к базе при старте приложения) не очень надежно. Поэтому по-хорошему нужно перед каждым взаимодействием с базой проверять, что соединение в порядке и в случае надобности делать новый коннект. Чтоб не делать этого каждый раз, есть удобная мидлвара, которая держит объект соединения в замыкании и при каждом запросе пользователя проверяет наличие соединения, делает рекконнект (если нужно), и записывает объект базы данных в req.
А можно просто взять mongoose и получить автоподключение + ORM.

Ну прямо скажем mongoose не решает вопрос отвалившейся БД.
Вот примерно так можно отслеживать состояние подключения: https://github.com/simdo/rest.api/blob/master/index.js

http://mongoosejs.com/docs/connections.html:
Note: The server option auto_reconnect is defaulted to true which can be overridden. The db option forceServerObjectId is set to false which cannot be overridden.

Note: If auto_reconnect is on, mongoose will give up trying to reconnect after a certain number of failures. Set the server.reconnectTries and server.reconnectInterval options to increase the number of times mongoose will try to reconnect.

// Good way to make sure mongoose never stops trying to reconnect
mongoose.connect(uri, { server: { reconnectTries: Number.MAX_VALUE } });


Т.е. в принципе нет нужны в таком функционале. Можно сделать watchdog таймер на событие отрубания и если оно не приконнектится через нужное время, значит база мертва / канал мертв.

Ну ребята, ну как же так?


  1. У меня rest микросервис.
  2. Данные обрабатываются в БД.
  3. БД удаленная.
  4. Соединение не стабильное.

Вопрос:
Какой ответ сервис должен отдать в случае когда клиент осуществляет api-запрос?
Сервер в данный момент утерял соединение с БД.


Варианты:


  • заставить клиента ожидать на линии
  • пробросить ошибку получения данных из БД ввиду отсутствия соединения?
  • ответить что в данный момент БД недоступна
  • ваши предложения?
Если соединение нестабильное, то у меня плохие новости — клиентам лучше поискать другие сервисы. Доступ к СУБД — одна из самых критичных к общей производительности системы вещей. Можно сделать локальное кеширование, сделать несколько копий базы с репликацией (но все-равно мастер на запись будет один). А вообще, да, 2 варианта:
1. Показывать пользователю крутилку, что мы что-то делаем с разумным временем ожидания
2. Отдать управление с показом варнинга, что операция продолжается в фоне и нужно подождать ее завершения (если настроены реплики с доступом на чтение — клиенту не обязательно ждать завершения операции сохранения для дальнейших операций поиска).
Опять же — все зависит от задачи, допускается такое поведение или нет.

Мой посыл заключается в том что приложение будет валить ошибки изза отсутствия связи с БД. Эти ошибки вываливать на фронт клиенту некошерно. Вы мне рассказываете что mongoose там что-то сам делает и хорошо.

так там автоматически создаётся пул из 5 соединений. Его размер хранится в db.poolSize
В db.url можно добавить параметр autoReconnect=true, хотя оно вроде бы было включено по-умолчанию
Еще дополнение. Где-то в документации натыкался, что лучше использовать express.Router()

Кстати, мне кажется что его удобнее использовать и не надо пробрасывать app внутрь.
а даже если и пробрасывать app — зачем пробрасывать db, когда ее можно сделать свойством app и обращаться через штатные set / get методы.
Это мне вопрос? Я не автор статьи.
Вопрос? Нет, это просто дополнение к комментарию — к мысли о том, что можно не прокидывать лишнее в виде параметров.
Ок, показалось что вопрос адресовался мне.

Интересно, что лучше: проброс app внутрь модуля или все же делать как middleware?

Сам делаю middleware.
app передается в каждом запросе как req.app. Т.е. инстанс клиента бд можно в момент инициализации сохранить в app через app.set(), а потом доставать в роутинге через req.app.get().
Использую mongooseJS в нем такая необходимость отсутствует.

Но если что-то нужно в middleware то да, правильный подход.
К сожалению описание, где это видел не помню. Но это не в официальной доке. Возможно где-то на GitHub в обсуждениях. Увы.

Но для себя выбрал путь, когда роутинг это отдельный файл с описанием всех АПИ, а middleware разложены по модулям и импортируются в роут. Это позволяет наглядно видеть все АПИ, и при этом быстро получить доступ к реализации. Так же это позволяет вынести определение АПИ из index.js который теперь состоит из импортов модулей и их подключений. Файл становится небольшим и легко читаемым.
/api/api_version/model_name

Не обязательно.
Есть несколько стратегий версионирования, и приведенная вами только одна из них.

Альтернативы:
1. Версия записывается в headers
2. Версионируем каждый из ресурсов, т.е. /api/model_name/api_version
3. ContentType, Accept
и т.д.
А, ну и вы вообще можете версионировать для каждого пользователя (так делают в Stripe, не совсем для каждого, но после регистрации для пользователя фиксируется версия api, т.е. пользователь совсем не знает какую версию он использует, и доку видит ту, которая для его api)
Stripe просто монстры API. Не каждый такое осилит реализовать.
Да это было просто пример. Я бы такое врятли стал в своих проектах делать, масштаб не тот.
Заинтересовался, что ж такого они наворотили, делюсь для будущих читателей комментариев.
stripe.com/blog/api-versioning

Гугл-перевод
Схемы управления версиями API
Общим подходом, позволяющим продвигать вперед веб-API, является использование версий. Пользователи определяют версию, когда они делают запросы, и поставщики API могут внести изменения, которые они хотят для своей следующей версии, и поддерживать совместимость в текущей. По мере выпуска новых версий пользователи могут обновляться, когда это удобно для них.

Это часто рассматривается как основная схема управления версиями с именами, такими как v1, v2 и v3, которые передаются в качестве префикса для URL-адреса (например, /v1/widgets ) или через HTTP-заголовок, например Accept. Это может работать, но имеет существенный недостаток в изменениях между версиями, которые настолько велики и настолько эффективны для пользователей, что это почти так же больно, как повторная интеграция с нуля. Это также не явная победа, потому что будет класс пользователей, которые не хотят или не могут обновиться и попасть в старые версии API. Поставщикам необходимо сделать сложный выбор между версиями API для выхода на пенсию и расширением, сокращающим этих пользователей, или сохранением старых версий навсегда при значительных затратах. Хотя поставщики, поддерживающие старые версии, могут показаться на первый взгляд полезными для пользователей, они также платят косвенно в виде снижения прогресса в улучшении. Вместо того, чтобы работать с новыми функциями, время разработки перенаправляется на сохранение старого кода.

В Stripe мы реализуем управление версиями со скользящими версиями, имена которых указаны с датой их выпуска (например, 2017-05-24 ). Несмотря на то, что они несовместимы в обратном направлении, каждый из них содержит небольшой набор изменений, которые делают инкрементные обновления относительно легкими, так что интеграция может оставаться актуальной.

В первый раз, когда пользователь делает запрос API, их учетная запись автоматически привязывается к самой последней доступной версии, и с этого момента каждый вызов API, который они делают, неявно назначается этой версией. Такой подход гарантирует, что пользователи не получают случайного изменения и делают первоначальную интеграцию менее болезненной, уменьшая количество необходимой конфигурации. Пользователи могут переопределять версию любого отдельного запроса, вручную устанавливая заголовок Stripe-Version или обновляя прикрепленную версию своей учетной записи на панели инструментов Stripe.

Некоторые читатели, возможно, уже заметили, что Stripe API также определяет основные версии с использованием префиксного пути (например, /v1/charges ). Хотя мы оставляем за собой право использовать это в какой-то момент, это вряд ли изменится в течение некоторого времени. Как отмечалось выше, основные изменения в версии, как правило, делают обновление болезненным, и нам сложно представить редизайн API, который достаточно важен, чтобы оправдать этот уровень воздействия пользователя. Наш нынешний подход был достаточным для почти сто несовместимых обновлений за последние шесть лет.

API versioning schemes
A common approach to allow forward progress in web APIs is to use versioning. Users specify a version when they make requests and API providers can make the changes they want for their next version while maintaining compatibility in the current one. As new versions are released, users can upgrade when it’s convenient for them.

This is often seen as a major versioning scheme with names like v1, v2, and v3 that are passed as a prefix to a URL (like /v1/widgets) or through an HTTP header like Accept. This can work, but has the major downside of changes between versions being so big and so impactful for users that it’s almost as painful as re-integrating from scratch. It’s also not a clear win because there will be a class of users that are unwilling or unable to upgrade and get trapped on old API versions. Providers then have to make the difficult choice between retiring API versions and by extension cutting those users off, or maintaining the old versions forever at considerable cost. While having providers maintain old versions might seem at first glance to be beneficial to users, they’re also paying indirectly in the form of reduced progress on improvements. Instead of working on new features, engineering time is diverted to maintaining old code.

At Stripe, we implement versioning with rolling versions that are named with the date they’re released (for example, 2017-05-24). Although backwards-incompatible, each one contains a small set of changes that make incremental upgrades relatively easy so that integrations can stay current.

The first time a user makes an API request, their account is automatically pinned to the most recent version available, and from then on, every API call they make is assigned that version implicitly. This approach guarantees that users don’t accidentally receive a breaking change and makes initial integration less painful by reducing the amount of necessary configuration. Users can override the version of any single request by manually setting the Stripe-Version header, or upgrade their account’s pinned version from Stripe’s dashboard.

Some readers might have already noticed that the Stripe API also defines major versions using a prefixed path (like /v1/charges). Although we reserve the right to make use of this at some point, it’s not likely to change for some time. As noted above, major version changes tend to make upgrades painful, and it’s hard for us to imagine an API redesign that’s important enough to justify this level of user impact. Our current approach has been sufficient for almost a hundred backwards-incompatible upgrades over the past six years.
*не увидел, что это перевод

Вопросы, которые я хотел бы задать автору:

А что делает ваше CRUD API именно REST API? Какая часть статьи показывает наличие REST функционала в вашем CRUD API?

вам надо понимать, что такое REST API, иметь представление об операциях CRUD
Не могли бы вы добавить в вашу статью хотя бы небольшое пояснение по поводу того, что такое CRUD и что такое REST, и как сделать REST API на основе CRUD операций, и чем это лучше например REST поверх RPC, и почему вы выбрали именно CRUD? И как сможете всегда-всегда обходиться четырьмя операциями в реальной жизни, т.к. если ввести хоть что-то, отличное от CRUD, то это уже будет не CRUD API, а просто некое API?

Освоив эту схему, вы сможете понять, как, с помощью Node, организовать практически любой необходимый REST-маршрут.
Не любое API с урлами является RESTful. Я честно говоря не вижу в вашем API ничего от REST.

пойду переведу и задам.
https://medium.com/@scottdomes/hi-igor-e5192ded55a1#.xsii8g1y7
Автор толком ничего не ответил. Я так понял, что автор считает, что если CRUD поверх HTTP, то этого достаточно, и это уже REST и всё тут. Гипермедийность с его стороны в беседе не упомянута. Хотя на мой взгляд это основополагающий принцип, отличающий «просто апи» от «рест апи» примерно так же как «просто текст» отличается от «текста с ссылками». Мне кажется отличие это разительное, и ситуация, когда вам нужно знать всю карту всех урлов заранее и она никак не вытекает из ответов сервера кардинально отличается от гипермедийного рест, где задумано, что, как и в HTML, вы получаете ссылки в ответах на запросы, и можете путешествовать дальше.

Подытожу: просто вываливать наружу методы хранилища проксируя через ноду недостаточно, чтобы называться REST.
как я понял из документации более лучшей практикой является использование express.Router() вместо передачи app как параметра.

в файле users.js
const express = require('express');
const router = express.Router();

router.get('/', function(req, res, next) {
  res.json({ a: 1 });
});

и в файле app.js
const express = require('express');
const app = express();

app.use('/users', require('./users'));
Для mongoDB версии 3.x код немного другой:
const express        = require('express');
const MongoClient    = require('mongodb').MongoClient;
const bodyParser     = require('body-parser');
const db             = require('./config/db');
const app            = express();
const port = 8000;
app.use(bodyParser.urlencoded({ extended: true }));
MongoClient.connect(db.url, (err, database) => {
  if (err) return console.log(err)
                      
  // Make sure you add the database name and not the collection name
  const db = database.db("note-api")
  require('./app/routes')(app, db);
  app.listen(port, () => {
    console.log('We are live on ' + port);
  });               
})
Спасибо!
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.