СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Feathers JS, часть вторая. За 5 минут мы создали серверное приложение, теперь разбираемся что же мы создали… в черновиках



Итак, в первой части мы использовали потрясающие возможности Feathers.js для того, чтобы за 5 минут создать backend для нашего приложения.

Наша система умеет работать с базами данных и может использовать аутентификацию. Но понятно, что базовых возможностей сгенерированного кода недостаточно для реализации всего необходимого нам функционала.

Поэтому пришла пора «поднять капот и заглянуть в потроха» нашего кода.

Итак, в результате работы генератора, у нас есть следующие файлы:



Давайте с ними разбираться.

Каталог public нам не интересен. Так (как и в Express) хранятся статические ресурсы, которые могут быть показаны при подключению к нашему северному приложению через браузер.
Изначально в этом каталоге хранится страница html, сообщающая о том, что вы запустили приложение Feathers.

В каталоге config находятся конфигурационные файлы («а ты думал обнаружить тут епископа?» /Джон Сильвер) для настройки работы нашего приложения. Их два, default используется в процессе разработки, а production — после оной.

Формат этих файлов одинаков:

{
  "host": "localhost",
  "port": 3030,
  "nedb": "../data/",
  "public": "../public/",
  "auth": {
    "token": {
      "secret": "bsnN3z77dVqo+HYUglvAm8pmTpECN159ZYriLpdXNqNMwEURjUV+f2qY2bCt0yuOJPb4rMMTQMJhf4s/LQPzAg=="
    },
    "local": {}
  }
}

Структура этого файла очень проста. Вначале указывается хост и порт, на котором работает сервер. Затем пути к каталогам с данными (в данном случае с файлами базы nedb) и к рассмотренному выше каталогу public. Ну и в самом конце — параметры для аутентификации, с которой нам ещё предстоит познакомиться.

В корне каталога src лежат два интересных файла. Первый и главный (но очень короткий) — index.js.

'use strict';
// Получаем экземпляр нашего приложения Feathers (подробнее рассмотрим чуть дальше)
const app = require('./app'); 

// Получаем номер порта (который взят из файла конфигурации)
const port = app.get('port'); 
// Запускаем наш сервер. Ура, товарищи!
const server = app.listen(port); 

// Как только сервер начнет работать, выводим сообщение об этом
server.on('listening', () =>
  console.log(`Feathers application started on ${app.get('host')}:${port}`)
);

Вся информация о работе с приложением сосредоточена во втором файле — app.js.

Ждёте, что он будет большим и сложным? Вынужден огорчить… Те, кто уже программировал на Express.js не увидят в нем чего-то особо нового. Для остальных приведу этот файл с моими комментариями:

'use strict';
const path = require('path');
const serveStatic = require('feathers').static;
const favicon = require('serve-favicon');
const compress = require('compression');
const cors = require('cors');
const feathers = require('feathers');
const configuration = require('feathers-configuration');
const hooks = require('feathers-hooks');
const rest = require('feathers-rest');
const bodyParser = require('body-parser');
const socketio = require('feathers-socketio');
const middleware = require('./middleware');
const services = require('./services');

// Создаем приложение Feathers
const app = feathers();

// Считываем и применяем тот самый файл конфигурации, который рассматривали абзацем выше
app.configure(configuration(path.join(__dirname, '..'))); 

// Подключаем к приложению различные модули
app.use(compress())
// CORS для любых доменов (помните как мы настраивали этот пункт в прошлый раз?)
  .options('*', cors()) 
  .use(cors())
// показывать в браузере иконку при открытии html-страницы
  .use(favicon( path.join(app.get('public'), 'favicon.ico') )) 
  .use('/', serveStatic( app.get('public') )) // использовать каталог public как главный для web-сервера
  .use(bodyParser.json()) 
  .use(bodyParser.urlencoded({ extended: true }))
// подключаем глобальные хуки Feathers, которые будут применяться ко всем запросам
  .configure(hooks())
// подключаем поддержку REST, теперь любой сервис автоматически доступен через этот протокол
  .configure(rest())
// подключаем поддержку Socket.io, теперь любой сервис автоматически доступен через этот протокол             
  .configure(socketio())      
// включаем все имеющиеся в приложении сервисы 
  .configure(services)        
// включаем всё middleware приложения (работает так же), как и в Express
  .configure(middleware); 
module.exports = app;

Основная прелесть в том, что ни один из этих файлов вам не нужно менять. Ну разве что по мелочи :)

Например можно добавить альтернативный логгер. Хотя — стоп! Зачем добавлять его в app.js, если мы автоматом подключили всё middleware и именно туда надо добавить логгер?!

Откроем папку middleware, а в ней файл index.js. Тут комментарии уже не мои, это генератор постарался.

'use strict';
const handler = require('feathers-errors/handler');
const notFound = require('./not-found-handler');
const logger = require('./logger');

module.exports = function() {
  // Add your custom middleware here. Remember, that
  // just like Express the order matters, so error
  // handling middleware should go last.
  const app = this;

  app.use(notFound());
  app.use(logger(app));
  app.use(handler());
};

То есть для того, чтобы добавить свой промежуточный модуль, достаточно:

1. Описать его в соответствующем файле в каталоге middleware.
2. Подключить с помощью require.
3. Добавить в цепочку с помощьюapp.use.

Например простое middleware not-found-handler.js, предназначенное для обработки запросов к несуществующим ресурсам, выглядит вот так:

const errors = require('feathers-errors');

module.exports = function() {
  return function(req, res, next) {
    next(new errors.NotFound('Page not found'));
  };
};

Как видите, каждое промежуточное ПО представляет собой функцию, которая возвращает функцию, в которой вызывается функция next() для перехода к следующему middleware. Да, всё как в express.js, да и модули от неё работают в Feathers.

А вот чего в Express нет, так это хуков. Делятся hooks на глобальные (работающие со всем приложением) и локальные для каждого сервиса. Hooks вызываются до и после вызова методов сервиса. Реализация самого простого хука предлагается нам генератором в файле hooks/index.js

exports.myHook = function(options) {
  return function(hook) {
    console.log('My custom global hook ran. Feathers is awesome!');
  };
};

No comments! Подключать его будем позже.

Уфф! Мы закончили с подготовительной частью и переходим к сердцу Feathers — сервисам.

Сервисы хранятся в каталоге services, состоят из файла index.js и подкаталога hooks, где хранятся специфичные для данного сервиса хуки.

Сервисы бывают обычные и для работы с базами данных. Обычные сервисы — это объекты, которые реализуют несколько методов (или часть из них):

  • find
  • get
  • create
  • update
  • patch
  • remove
  • setup

После того как метод реализован, Feathers будет запускать его в ответ на соответствующий запрос по протоколам REST, Socket.io или Primus. Напомню, прелесть в том, что вам не нужно подключать обработчики этих протоколов — просто реализуйте методы!

Каждый из методов должен возвращать объект класса Promise, например вот так:

get(id, params) {
  return Promise.resolve({
    id,
    read: false,
    text: `Feathers is great!`,
    createdAt: new Date().getTime()
  });

В результате, после запроса типа GET, в браузер будет возвращаться соответствующий объект, упакованный в json (помните, что мы подключали поддержку json в app.js?)

В следующем примере я приведу одновременно и шаблон для создания сервиса (ровно то же создаст для вас генератор) и соответствие между методами сервиса и запросами REST.

const myService = {
  // GET /messages
  find(params [, callback]) {},
  // GET /messages/<id>
  get(id, params [, callback]) {},
  // POST /messages
  create(data, params [, callback]) {},
  // PUT /messages[/<id>]
  update(id, data, params [, callback]) {},
  // PATCH /messages[/<id>]
  patch(id, data, params [, callback]) {},
  // DELETE /messages[/<id>]
  remove(id, params [, callback]) {},
  setup(app, path) {}
}

А для того, чтобы при работе с базой данных, не реализовывать эти методы вручную — генератор создает для вас специальные сервисы. Выглядят они вот так (комментарии мои):

const path = require('path');
// подключаем библиотеку для работы с нужной нам базой (в данном случае NeDB)
const NeDB = require('nedb');
// и библиотеку Feathers для взаимодействия с этой базой
const service = require('feathers-nedb');
// подключим локальные хуки
const hooks = require('./hooks');

module.exports = function(){
  const app = this;

// определим параметры для работы с базой
// каталог возьмем из конфигурационного файла, имя базы по умолчанию = имя сервис
  const db = new NeDB({
    filename: path.join(app.get('nedb'), 'contacts.db'),
// этот параметр означает автоматическую загрузку данных, в противном случае данные нужно 
// будет подгружать вручную
    autoload: true
  });

//определяем параметры БД
  let options = {
    Model: db,
// передается ли информация целиком или постранично
    paginate: {
      default: 5,
      max: 25
    }
  };

// Дальше комментарии генератора весьма полезны

  // Initialize our service with any options it requires
  app.use('/contacts', service(options));

  // Get our initialize service to that we can bind hooks
  const contactsService = app.service('/contacts');

  // Set up our before hooks
  contactsService.before(hooks.before);

  // Set up our after hooks
  contactsService.after(hooks.after);
};

Единственны минус такого подхода в том, что запросы по факту формируются на стороне клиента и напрямую передаются в базу для обработки.

Вы спросите у меня — а как же быть, если нужно проверить запрос от пользователя, изменить его или поправить набор возвращаемых данных;

Ха, отвечу я вам! А для этого у нас есть хуки!

По умолчанию для каждого сервиса создается свой файл index.js, в который вы можете поместить специфичные хуки.

 // подключили глобальные хуки
const globalHooks = require('../../../hooks');
// подключили стандартные хуки Feathers
const hooks = require('feathers-hooks');
// описываем специфичные хуки, которые будут вызываться ДО вызова сервиса
// например для проверки параметров
exports.before = {
  all: [],
  find: [],
  get: [],
  create: [],
  update: [],
  patch: [],
  remove: []
};
// описываем специфичные хуки, которые будут вызываться ПОСЛЕ вызова сервиса
// например для изменения полученных из базы результатов
exports.after = {
  all: [],
  find: [],
  get: [],
  create: [],
  update: [],
  patch: [],
  remove: []
};

Хуки определяются как функции. Каждый хук ДО получит следующие параметры:

  • method — имя вызываемого метода
  • type — hook type (before или after)
  • params — параметры, переданные сервису
  • data — данные запроса (для методов create, update и patch)
  • app — объект app
  • id — id (для get, remove, update и patch)

Хуки ПОСЛЕ дополнительно получают еще и параметр result — результат запроса.

Ну и вишенка на торте! Есть готовые хуки, которые позволяют выполнить стандартные операции с данными. Вот, например, хук remove позволяет удалить некоторые поля из результатов запроса или из самих запросов:

app.service('users').before({
  create: hooks.remove('_id'),
  update: hooks.remove('_id'),
  patch: hooks.remove('_id})

Т.е. при использовании запросов create, update и patch из запросов будет удалено поле _id
app.service('users').after(hooks.remove('password', 'salt'));

А тут мы из результата запроса удалим поля password и salt.

Таких буков достаточно много, все они описаны здесь.

Ура! Мы практически закончили с теорией и в третьей части сможем написать что-то сами. Продолжение следует :)

Маленький анонс
Как вы наверное видите из оформления блога, я работаю в компании, которая создает систему учета рабочего времени Kickidler.
Большинство из вас к контролю относится отрицательно и как программист я вас понимаю.
Но поскольку программу хорошо покупают, то выходит, что руководители имеют немного другое мнение.

Поэтому в свое свободное время я решил сделать систему личной продуктивности. Она нужна для того, чтобы мы сами знали на что тратим свою жизнь :) Система не будет ничего никому отсылать, она попробует помочь вам (тем, кто этого действительно хочет) стать немного эффективнее.
Написание этой системы было своего рода экспериментом — она целиком создана на JavaScript, даже агент собирающий информацию. В качестве работает Feathers, на клиенте React, данные хранятся в NeDB.

Так вот, в течение ближайшего месяца у меня в планах представить систему на тестирование. Поддерживаться будут Windows и MacOS X, отчетов будет минимальное количество. Но, я буду ждать ваших отзывов и (самое главное!) предложений о том, какой функционал вам нужен.
+15
~6500

комментарии (6)

+1
S_A ,  
Feathers.js очень интересен. Подобные знания точно лишними не назовешь.А вот на анонс и поставленную в нём ситуацию отзовусь.

Контроль рабочего времени программистов — это как контроль рабочего времени математика. Решит задачу за указанное число минут? Если нет, плохой математик? Наказать рублем? И прочие вытекающие.

Дело не в программистах и не в эффективности. Эффективность — это не предмет микроменеджмента. Это может работать в случае работы, где хватай больше кидай дальше. Да и то условно. Раздробив задачу на малые части и контролируя эффективность каждой части вы все равно не получите точный результат, потому что линейная логика тут не работает (и это ошибка руководителей, который мыслят линейно: удвоим число продавцов следовательно удвоим продажи).

Поэтому из пожеланий к (пусть) личной системе эффективности только одно — подсчет эффективности: соотношения эффекта к затратам. Я могу потратить больше времени, но получить больший эффект. Вот как раз учета эффектов ни в одной системе учета времени нет.
0
Andrew_I ,   * (был изменён)
Давайте я поясню в чем смысл системы личной эффективности IMHO.
Вот есть фрилансер. Он весь день работает и не успевает. Ему интересно почему.
Он смотрит в систему и видит, что 3 часа читал новости. Ему-то казалось, что всего минут двадцать, а на самом деле три часа.
Если ему комфортно с этим жить, то никто не против. Статистика доступна только ему, она хранится на локальном компе и никуда не отсылается. Агент специально работает в открытом режиме.
А вот если он хочет больше времени тратить на работу (чтобы больше денег заработать) — то используя данную программу, снизит количество непродуктивного времени до комфортного ему уровня.
Если под эффектом понимать выполненный проект, то да соотношение эффекта к затратам можно подсчитать. Если что-то еще, то присылайте предложения как это сделать и какие данные для этого нужны.
+1
S_A ,  
Суть проста. Эффективность = эффект / вложения.

Моя мысль в том, что эти новости этого фрилансера могли так вдохновить, что после (возможно не очень удачного) проекта написал стартап на X000 USD в месяц дохода. Если говорить про его эффективность, а не про эффективность проекта.

Вложения — это трудозатраты. Эффект — доход от трудозатрат. Я не говорю, что именно в USD. Попугаи могут быть разные (особенно если это не фрилансер).
0
Andrew_I ,  
В данном случае эффективность весьма эфемерна.
Вот если из-за погоды у меня болит голова, то половину дня я могу поработаь, а половину посмотреть фильм. От этого я буду только свежее и на следующий день сделаю больше.
А если и на следующий день, когда я буду чувствовать себя хорошо, я опять фильмы буду смотреть, то значит я лентяй.
Если мен устраивает быть лентяем, то все в порядке. А если нет, то задача программы просигнализировать мне об этом.
А насчет попугаев — их количество далеко не всегда зависит от времени. А прочие факторы измеримы с большим трудом. Если знаете как — скажите, я реализую ))
+1
S_A ,  
Я много знаю как, но все они в вашу схемотехнику не укладываются. У вас все примеры про микротаймменеджмент. Снова, в нём нет ни эффектов, ни эффективности. За деревьями лес теряется, об этом в предыдущем комменте.

Последняя наводка. Посмертный учет потраченного бессмысленнен, кроме как питать депрессии. Имеет смысл прогресс план/факт. Поэтому в системе личной эффективности должны быть цели и отклонения.
0
yogurt1 ,  

Feathers под собой использует Express
Попробуйте еще вот это вот: https://github.com/pleerock/routing-controllers
Совместимо с Koa также