Создание API и сервисов REST с помощью Node.js и Express — это просто и понятно. Использование некоторых принципов, таких как разделение ответственности и модульность, может привести к созданию надежной и расширяемой кодовой базы (см. другую статью, которую я написал на эту тему здесь). Когда у вас есть система, начинается настоящее веселье. Поддержание кода и высокая уверенность при изменении кода становятся довольно сложными по мере взросления проекта и роста команды. Я хотел бы дать здесь несколько советов, особенно в том, что касается приложений Express. Принципы должны быть (читай: надеюсь) достаточно естественно применимы и к другим системам.

Группировка связанного кода

Один из уроков, которые я усвоил, работая над несколькими службами на основе Express за последние несколько лет, заключается в том, что разделение и группировка связанного кода в модули невероятно полезны. По крайней мере, это значительно облегчает понимание изолированных модулей. В терминологии Express это означает группировку связанных маршрутов в отдельные маршрутизаторы. Если вы согласны с принципами REST, вы можете решить разместить все операции над конкретным объектом на его собственном изолированном маршрутизаторе.

Написание службы в виде набора маршрутизаторов имеет важное преимущество, заключающееся в обеспечении разделения задач, модульности и поощрении тестирования на уровне API, а не на уровне реализации. Я признаю, что это предложение изобилует модными словечками, поэтому позвольте мне подкрепить свои утверждения некоторым примером кода.

библиотека/маршрутизаторы/модели.кофе:

Model = require '../models/model'
responseHandler = (res, next, successCode, errorCode) ->
  (err, model) ->
    return next err if err

    return res.status(errorCode).end() unless model

    res.status(successCode).json model.toJSON()
module.exports = (router, app) ->
  router.get '/:id', (req, res, next) ->
    Model.findById req.params.id,
      responseHandler res, next, 200, 404
  router.post '/', (req, res, next) ->
    model = new Model req.body
    model.create responseHandler 202, 400

библиотека/маршрутизаторы/index.coffee:

module.exports =
  '/api/models': require './models'

библиотека/server.coffee:

routers = require './routers'
app = express()
...
for route, configFn of routers
  router = express.Router()
  configFn router, app
  app.use route, router

Что касается модульности, вы можете монтировать маршрутизаторы, которые вы определяете, по любому базовому пути, который вы хотите, в файле реестра маршрутизатора lib/routers/index.coffee. Вы можете написать построитель маршрутизатора, который принимает тип модели в качестве параметра и создает маршрутизатор для общих конечных точек RESTful для этой модели. Это позволит вам использовать один и тот же код маршрутизатора для каждого типа модели в вашем приложении.

С точки зрения разделения задач каждый маршрутизатор должен объявлять свои зависимости отдельно от сервера, включая регистрацию промежуточного программного обеспечения и взаимодействие с самим приложением Express. Маршрутизаторы, которые имеют общую функциональность, должны выделять эту функциональность в отдельные модули (возможно, какое-то общее промежуточное программное обеспечение или утилиты), что, опять же, поощряет модульность и тестируемость.

Я коснусь тестирования в следующем разделе.

Услуги по тестированию

Отдельные маршрутизаторы облегчают тестирование. Использование супертеста вместе с помощником по тестированию, который может создавать и настраивать маршрутизаторы, упрощает тестирование вашего API, а не вашей реализации. Не убежден? Давайте посмотрим на пример кода.

Учитывая приведенный выше маршрутизатор, вы можете протестировать несколько разных частей API. Во-первых, вы должны быть уверены, что создание модели на самом деле создает модель. Во-вторых, вы, вероятно, захотите протестировать некоторые случаи ошибок, такие как GET для отсутствующей модели и POST с неверными данными.

...
describe 'Model Router', ->
  beforeEach ->
    configFn = require "../lib/routers/models"
    router = express.Router()
    app = express()
    app.use bodyparser.json()
    configFn router, app
    app.use '/', router
    @app = supertest app
  describe 'POST /', ->
    it 'creates a new model', (done) ->
      @app.post('/')
        .set('Content-Type', 'application/json')
        .send({data: 'model data here'})
        .expect(202)
        .end done
  ...
  describe 'GET /:id', ->
    ...
    it 'returns a 404 for a missing model', (done) ->
      @app.get('/some_missing_id')
        .expect(404)
        .end done

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

Детерминированные тесты

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

Если вы используете Mocha с Node.js, это так же просто, как использовать вспомогательный сценарий тестирования, который выполняется перед любыми тестами.

тест/test_helper.coffee

before (done) ->
  rebuildDatabase done
afterEach (done) ->
  truncateTables done

Вывод

Я надеюсь, что убедил вас в преимуществах использования небольших модульных маршрутизаторов и тестирования на уровне API без побочных эффектов в ваших приложениях Express. Это может показаться большой работой за очень небольшое вознаграждение, но я могу заверить вас, что через пять, семь или двенадцать месяцев вы оцените инвестиции, которые вы сделали в свою кодовую базу. Удачного взлома!