В этой статье я подробно описываю, как я создал тонкий слой поверх FeathersJS, чтобы сделать приложение более готовым к производству. Универсальная конфигурация предоставляет расширяемые параметры для различных сред развертывания аналогичным образом на стороне клиента / сервера. Это основа общепринятых производственных практик, таких как:
- иметь возможность настроить транспорт, который будет использоваться для обхода брандмауэров / проблем безопасности,
- выполнить префикс API для одновременного развертывания нескольких версий,
- сделать уровень журнала настраиваемым для отслеживания невоспроизводимых ошибок.
Такой подход был использован для успешной поддержки проекта Weacast.
Универсальная конфигурация
Что касается конфигурации, FeathersJS уже предоставляет небольшую оболочку над node-config; на стороне клиента, однако, дополнительных функций не предусмотрено. Я также хотел иметь возможность управлять различными файлами конфигурации развертывания. Поскольку я использую webpack для сборки своего клиентского приложения, я решил также интегрировать node-config для внешнего интерфейса. Итак, я добавил это в свою сборку:
var fs = require(‘fs’) // Load config based on current NODE_ENV value var clientConfig = require(‘config’) // Save it to ‘config/client-config.json’ fs.writeFileSync(path.join(‘config’, ‘client-config.json’), JSON.stringify(clientConfig))
И псевдоним в моей конфигурации WebPack:
webpackConfig = { resolve: { … alias: { config: path.resolve(__dirname, ‘./client-config.json’) } }, … }
Этот псевдоним позволяет стандартным образом интегрировать этот конфигурационный файл в клиентские файлы:
import config from ‘config’
Основное отличие от конфигурации на стороне сервера заключается в том, что эта конфигурация создается во время сборки, а не во время выполнения, потому что на клиенте нет запущенного процесса NodeJS, это просто статический импорт файла. в конце. Однако вы извлекаете выгоду из мощности node-config для управления сложными конфигурациями. На самом деле лучше всего держать их независимыми и делиться частями, когда это необходимо. В самом деле, вы не хотели бы утечки переменных среды частного сервера в выходных данных конфигурации клиента, что могло бы вызвать серьезные проблемы с безопасностью.
Префикс API
Во-первых, я добавил свойство apiPath
в файлы конфигурации приложения, определяющее используемый префикс API. Затем, чтобы избавиться от префикса API внутри при объявлении / извлечении службы, я добавил небольшой набор функций поверх экземпляра приложения Feathers на стороне внешнего / внутреннего интерфейса. Действительно, получение сервисов Feathers всегда основывается на пути, а не на имени сервиса, поэтому вам необходимо знать детали префикса API:
app.declareService = function (name, app, service) { const path = app.get('apiPath') + '/' + name // Initialize our service app.use(path, service) return app.getService(name) } app.getService = function (name) { return app.service(app.get('apiPath') + '/' + name) }
При настройке транспортов на бэкэнд-стороне вы должны предоставить другую конечную точку для REST / Websocket, чтобы избежать конфликта, поэтому в моем случае я изменил конфигурацию socketio по умолчанию:
app.configure(rest()) app.configure(socketio({ path: app.get('apiPath') + 'ws' }))
Аналогично на стороне клиента:
import config from 'config' let socket = io(window.location.origin, { path: config.apiPath + 'ws' }) app.configure(feathers.socketio(socket))
Уровень аутентификации
Чтобы аутентификация работала с префиксом API, вам необходимо настроить конфигурацию по умолчанию, на стороне бэкенда файл конфигурации выглядит так:
var API_PREFIX = '/api' module.exports = { apiPath: API_PREFIX, authentication: { ... path: API_PREFIX + '/authentication', service: API_PREFIX + '/users' }, ...
Тогда на стороне клиента настройка конфигурации FeathersJS будет выглядеть так:
app.configure(feathers.authentication({ storage: window.localStorage, path: config.apiPath + '/authentication' }))
Позаботьтесь о настройке ваших транспортов (REST / Websocket) перед аутентификацией, в противном случае некоторые требуемые хуки (заполнение заголовков и т. д.) могут быть неправильно зарегистрированы.
Транспорт
Как было показано ранее, серверное приложение предоставляет конечную точку REST, а также конечную точку Websocket, последнее, что нужно сделать, - это выбрать правильный вариант в клиенте в соответствии с конфигурацией:
import config from 'config' let app = feathers() app.configure(hooks()) if (config.transport === 'websocket') { let socket = io(window.location.origin, { transports: ['websocket'], path: config.apiPath + 'ws' }) app.configure(feathers.socketio(socket)) } else { app.configure(feathers.rest(window.location.origin).fetch(window.fetch.bind(window))) }
логирование
Когда вы работаете с FeatherJS, вы быстро понимаете, что хуки - это первоклассные граждане, особенно на стороне сервера, но они также полезны и на стороне клиента. Используя хуки, я создал простой настраиваемый уровень журнала с обеих сторон, используя winston для бэкэнда и loglevel для внешнего интерфейса. Хуки очень похожи, бэкэнд уже сгенерирован FeathersJS CLI, так что он предназначен для клиента (hooks / logger.js):
import logger from 'loglevel' export function log (hook) { let message = `${hook.type}: ${hook.path} - Method: ${hook.method}` if (hook.type === 'error') { message += `: ${hook.error.message}` } logger.debug(message) if (hook.error) { logger.error(hook.error) } if (hook.data) { logger.trace(hook.data) } if (hook.params) { logger.trace(hook.params) } if (hook.result) { logger.trace(hook.result) } }
Настройте его в клиентском приложении следующим образом:
import logger from './hooks/logger' app.hooks({ before: { all: [ logger ] }, after: { all: [ logger ] }, error: { all: [ logger ] } })
И прочтите уровень журнала, который будет использоваться из конфигурации при запуске приложения:
import logger from 'loglevel' import config from 'config' let app = feathers() if (config.logs && config.logs.level) { logger.setLevel(config.logs.level, false) } else { logger.setLevel('info') }
Ошибка отлова
Еще одно полезное использование обработчиков на стороне клиента (отчасти связанное с ведением журнала) - это глобальный перехват ошибки, как это возможно на внутренней стороне, путем вывода общего всплывающего сообщения или чего-то подобного в пользовательском интерфейсе. Однако это больше привязано к технологии внешнего интерфейса, которую вы используете, чем к ведению журнала. Например, в Weacast мы реализовали это в Vue.js с помощью хука, поднимающего глобальные события на шине:
import EventBus from '../event-bus' export function emit (hook) { EventBus.$emit(hook.type + 'Hook', hook) }
Эти события затем перехватываются такими компонентами управления ошибками:
EventBus.$on('errorHook', hook => { // Display an alert, possibly depending on the hook properties })
Думаю, у вас возникла идея сделать так, чтобы это работало с React или Angular.
Заключение
Я надеюсь, что знание того, как сделать ваше приложение FeathersJS более настраиваемым, будет вам полезно, чтобы получить более полный пример, вы можете взглянуть на проект Weacast:
- Оболочка серверного приложения
- Оболочка клиентского приложения
- Файл конфигурации на стороне сервера
- Файл конфигурации на стороне клиента
Если вам понравилась эта статья, не стесняйтесь взглянуть на наши решения с открытым исходным кодом и насладиться нашими другими статьями о Feathers, команде Kalisio!