-
-
Save artello73/e10f0bbc6cd11f5083472a7455543c68 to your computer and use it in GitHub Desktop.
Revisions
-
zmts revised this gist
Feb 16, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -239,7 +239,7 @@ router.get('/api/users', function(req, res){ __Выходит такой Lifecycle >> Запрос проходит дефолтные мидлвари >> Попадает в контроллер >> Попадает в `actionRunner` тот проверяет права доступа текущего юзера к экшену, валидирует запрос и стартует процесс обработки (статическая ф-ция `run`) >> В экшене выполняется бизнес логика и результат возращается в контроллер >> `actionRunner` получает результат и возвращает его клиенту.__ Что не стоит делать в контроллере: - Валидировать параметры(`req.params`). Как показала практика это плохая идея. Проверку параметров лучше делать непосредственно в экшене. Таким образом в дальнейшем будет более наглядно видно какие парметры в запросе доступны экшену. ## Actions Основным ключевым моментом является использование отдельного класса для каждого эндпоинта. Я определяю понятие `Action` как класс инкапсулирующий всю логику работы эндпоинта. То есть для реализации круда у нас будет 5 файлов (`CreatePostAction`, `GetPostByIdAction`, `UpdatePostAction`, `DeletePostAction`, `ListPostsAction`) по экшену на каждый эндпоинт. -
zmts revised this gist
Feb 16, 2020 . 1 changed file with 11 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -35,7 +35,7 @@ Controller(роутинг, проверка прав по роли) >> Action(п - [Controllers](#controllers) - [Actions](#actions) - [Model](#model) - [Agents](#agents) - [Providers](#providers) - [Helpers](#authhelpers) - [DAO](#dao) @@ -376,20 +376,20 @@ async function getById (id, options = {}) { } ``` ## Agents Агенты - это врапперы над внешними API/клиентами. Предназначение данной абстракции обеспечение единого контракта работы с внешними ресурсами. Допустим нам необходимо использовать в качестве хранилища файлов сервис от Амазон AWS S3. Мы создаем `S3Agent` добавляем в него свои методы-обертки. В случае критического изменения в клиенте амазона мы соотвественно меняем методы обертки без ущерба и переписывания остальной логики в остальных частях приложения. Перехваченные ошибки внешних API выбрасываем предватительно обернув их в свой кастомный класс ошибки (дабы иметь единый интервейс работы с ошибками). <details> <summary>agent example:</summary> ```js const { assert, AppError, errorCodes } = require('supra-core') const AWS = require('aws-sdk') const $ = Symbol('private scope') class S3Agent { constructor (options) { assert.object(options, { required: true, notEmpty: true }) assert.string(options.access, { required: true, notEmpty: true }) @@ -445,17 +445,17 @@ module.exports = S3Client </details> ## Providers Как быть в ситуации когда необходимо использовать один и тот же агент в нескольких экшенах ? Для этого случая предназанчен слой `Providers`. Дабы не плодить для каждого экшена свой агент создаем необходимое единожды в провайдере, а дальше импортируем его в нужных местах. <details> <summary>code:</summary> ```js const S3Agent = require('../core/clients/S3Agent') const config = require('../config') class RootProvider { constructor () { this.s3Agent = new S3Agent({ access: config.s3.access, secret: config.s3.secret, bucket: config.s3.bucket @@ -554,9 +554,9 @@ testControllerHandler (req, res, next) { ```js // IS GOOD testControllerHandler (ctx) { // my logic with throwed error await testService.getTest({ id: ctx.params.id }) } ``` </details> -
zmts revised this gist
Feb 16, 2020 . 1 changed file with 5 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,9 +16,11 @@ code: https://github.com/zmts/supra-api-nodejs #### Несколько слов про разделение приложения на слои: Обычно в слое контроллеров данные из запроса валидируются и приводятся к виду необходимому для последующего сервисного слоя. В свою очередь сервисный слой полностью изолирует бизнес логику и возвращает результат вычислений в контроллер. Но _иногда_, а может и немного _чаще_ в контроллер просачивается бизнес логика, а в сервисный слой валидация, а то и вообще контекст запроса. Дабы так не происходило данный подход предлагает использовать единый слой для всей логики касаемо конкретного `use case`. Назовем этот слой `Action layer`. В итоге имеем: 1. Минималистичный контроллер - маппинг экшенов на роуты 2. Экшен - определяет правила валидации входящих данных, проверки прав доступа(владелец/не владелец/админ/не админ/аноним...) и бизнес логику 3. Data layer - прослойка к БД ``` -
zmts revised this gist
Feb 16, 2020 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -346,12 +346,12 @@ class PostModel extends BaseModel { } } module.exports = PostModel ``` </details> Каждое поле модели это инстанс класса `Rule` состоящий из валидатора и описания. Выполнение ф-ции валидации обязано вернуть либо булево значение либо строку(`error.message`) Все последующие проверки полей в других компонентах приложения обязаны импортироваться из схемы модели. Например в cлое `DAO` в неком `getById(id)` вместо того что-бы делать: ```js async function getById (id, options = {}) { -
zmts revised this gist
Feb 16, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -250,7 +250,7 @@ __Выходит такой Lifecycle >> Запрос проходит дефо Правила валидации (`validationRules`) импортируются из модели или в случае необходимости используются кастомные. ***Роль модели в экшене:*** Дабы не дублировать кучу подобных моделей(одна модель для создания сущности со всеми `required` полями, другая для обновления, третья для обновления указанного набора полей итд...) было принято решение не указывать в ф-ции валидации модели `required` требование. Вместо этого `required` флаг имеет место быть в момент валидации как опция класса `RequestRule`. ```js { validationRules: { -
zmts revised this gist
Feb 16, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -26,7 +26,7 @@ Controller(роутинг, проверка прав по роли) >> Action(п ``` ## Содержание - [Assert](#assert) - [Config](#config) - [Server](#serverapp-initialization) - [Middlewares](#middlewares) -
zmts revised this gist
Feb 16, 2020 . 1 changed file with 4 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -41,8 +41,10 @@ Controller(роутинг, проверка прав по роли) >> Action(п - [Logging](#logging) - [Policy/Roles/Permissions](#policyrolespermissions) ## Assert > Assert — это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы. Эта конструкция может автоматически сигнализировать при обнаружении некорректных данных, что обычно приводит к выбросу исключения с указанием места обнаружения некорректных данных. https://habr.com/ru/post/141080/ В этом мне помогает небольшой [Assertion class](https://github.com/zmts/supra-api-nodejs/tree/master/core/lib/assert). Из существующих библиотек есть [node-assert-plus](https://github.com/joyent/node-assert-plus). ```js async function foo (id, options = {}) { assert.integer(id, { required: true }) -
zmts revised this gist
Jan 30, 2020 . 1 changed file with 17 additions and 14 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -475,28 +475,31 @@ module.exports = new RootProvider() ## Errors Работа с ошибками организована через единственнный кастомный класс ошибки и список эррор кодов (хранящийся в виде конфига), это позволяет не создавать на каждый тип ошибки свой класс. ```js class AppError extends Error { constructor (options) { if (!options || !options.description) throw new Error('description param required') super() this.description = options.description || undefined // default error description from errorCodes this.message = options.message || this.description // message thrown by error this.status = options.status || 500 this.code = options.code || 'UNEXPECTED_ERROR' this.layer = options.layer || undefined this.meta = options.meta || undefined this.req = options.req || undefined this.origin = options.origin || undefined // origin error data } } ``` ```js module.exports = { ACCESS: { description: 'Access denied', status: 403, code: 'ACCESS_ERROR' }, BAD_ROLE: { description: 'Bad role', status: 403, code: 'BAD_ROLE_ERROR' }, DB: { description: 'Database error occurred', status: 500, code: 'DB_ERROR' } } ``` ```js new AppError({ ...errorCodes.ACCESS, message: 'Access denied dude !!!' }) ``` В последствии на фронте выводим ошибку из поля `message` или то что фронтенд посчтитает нужным в зависимости от поля `code`. @@ -616,7 +619,7 @@ return new Promise((resolve, reject) => { if (currentUser.role === roles.superadmin) return resolve() if (permissions[currentUser.role].includes(accessTagAll)) return resolve() if (permissions[currentUser.role].includes(accessTag)) return resolve() return reject(new AppError({ ...errorCodes.ACCESS, message: 'Access denied, don\'t have permissions.' })) }) ``` </details> @@ -632,9 +635,9 @@ return new Promise((resolve, reject) => { if (user.id === model.userId) return resolve(model) if (!model.private) return resolve(model) if (model.private) { return reject(new AppError({ ...errorCodes.ACCESS, message: `User ${user.id} don't have access to model ${model.id}` })) } return reject(new AppError({ ...errorCodes.ACCESS })) }) ``` </details> @@ -671,7 +674,7 @@ module.exports = GetPostByIdAction return new Promise((resolve, reject) => { if (user.role === roles.superadmin) return resolve() if (user.id === model.userId) return resolve() return reject(new AppError({ ...errorCodes.ACCESS })) }) ``` </details> -
zmts revised this gist
Jan 30, 2020 . 1 changed file with 12 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -375,10 +375,13 @@ async function getById (id, options = {}) { ## Clients `Clients` - это врапперы над внешними API/клиентами. Предназначение данной абстракции обеспечение единого контракта работы с внешними ресурсами. Допустим нам необходимо использовать в качестве хранилища файлов сервис от Амазон AWS S3. Мы создаем `S3Client` добавляем в него свои методы-обертки для работы с данным хранилищем. В случае критического изменения в клиенте амазона мы соотвественно меняем методы обертки без ущерба и переписывания остальной логики в остальных частях приложения. Единожды создав экземпляр клиент-враппера, можно переиспользовать его в разных местах вместо того что бы плодить инстансы голого клиента. Перехваченные ошибки внешних API выбрасываем предватительно обернув их в свой кастомный класс ошибки (дабы иметь идиный интервейс работы с ошибками). <details> <summary>client example:</summary> ```js const { assert, AppError, errorCodes } = require('supra-core') const AWS = require('aws-sdk') const $ = Symbol('private scope') @@ -388,6 +391,7 @@ class S3Client { assert.string(options.access, { required: true, notEmpty: true }) assert.string(options.secret, { required: true, notEmpty: true }) assert.string(options.bucket, { required: true, notEmpty: true }) assert.instanceOf(options.logger, AbstractLogger) AWS.config.update({ accessKeyId: options.access, @@ -396,10 +400,11 @@ class S3Client { this[$] = { client: new AWS.S3(), bucket: options.bucket, logger: options.logger } this[$].logger.debug(`${this.constructor.name} constructed...`) } async uploadImage (buffer, fileName) { @@ -419,8 +424,11 @@ class S3Client { this[$].client.upload(params, (error, data) => { if (error) { return reject(new AppError({ ...errorCodes.EXTERNAL, message: `${this.constructor.name}: unable to upload object. ${error.message}`, origin: error })) } resolve(data.Location) }) -
zmts revised this gist
Jan 21, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,7 +16,7 @@ code: https://github.com/zmts/supra-api-nodejs #### Несколько слов про разделение приложения на слои: Обычно в слое контроллеров данные из запроса валидируются и приводятся к виду необходимому для последующего сервисного слоя. В свою очередь сервисный слой полностью изолирует бизнес логику и возвращает результат вычислений в контроллер. Но _иногда_, а может и немного _чаще_ в контроллер просачивается бизнес логика, а в сервисный слой валидация, а то и вообще контекст запроса. Дабы так не происходило данный подход предлагает использовать единый слой для всей логики касаемо конкретного `use case`. Назовем этот слой `Action layer`. В итоге имеем: 1. Минималистичный контроллер - сугубо маппинг экшенов на роуты 2. Слой экшенов отвечающий за валидацию, всевозможные проверки доступа(владелец/не владелец/админ/не админ/аноним...) и бизнес логику 3. Data layer - прослойка к БД -
zmts revised this gist
Dec 30, 2019 . 1 changed file with 15 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -55,6 +55,8 @@ async function foo (id, options = {}) { `Assertion` это набор статических методов для базовой проверки типов аргуметров и возможно некоторых дополнительных опций (аля `{ required: true, notEmpty: true, positive: true }`). ## Config Конфиг это святая-святых настроек приложения посему должен инстансироватся и инициализироватся(через асинхронные методы) перед HTTP сервером. Все параметры неободимые для работы приложения должны хранится в едином конфиге. Параметры конфига делятся на два типа: констаннты и переменные. Первые храним как обычные поля, вторые извлекаем из переменных окружения (для примера это: логины/пароли доступа в БД, ключи для шифрования сессий или JWT токенов, ключи доступа к другим внешним ресурсам итд...). ```js class AppConfig extends BaseConfig { @@ -65,6 +67,19 @@ class AppConfig extends BaseConfig { this.host = this.set('APP_HOST', this.joi.string().required(), 'localhost') this.name = this.set('APP_NAME', this.joi.string().required(), 'SupraAPI') this.foo = 'bar' this.dbUser = '' this.dbPassword = '' } async getSomeAsyncCredentials () { const { data } = await getDBCredentials() this.dbUser = data.user this.dbPassword = data.password } async init () { await getSomeAsyncCredentials() logger.debug(`${this.constructor.name}: Initialization finish...`) } } ``` -
zmts revised this gist
Nov 25, 2019 . 1 changed file with 30 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -478,27 +478,36 @@ new ErrorWrapper({ ...errorCodes.ACCESS, message: 'Access denied dude !!!' }) В последствии на фронте выводим ошибку из поля `message` или то что фронтенд посчтитает нужным в зависимости от поля `code`. ### Обработка ошибок <details> <summary>Опишу частую ошибку встречающуюся во многих проектах на просторах интернета</summary> ```js // IS BAD testControllerHandler (req, res, next) { try { // my logic with throwed error await testService.getTest({ id: req.params.id }) } catch (error) { res.send(error) // это и есть локальная обработка ошибки } } ``` </details> Все ошибки обязаны быть перехваченны в единственном месте, в глобальном обработчике ошибок(глобальная `error middleware`). Остальные `unexpected` ошибки аля `unhandledRejection` в соотвествующих им хендлерам. __Это означает никаких try/catch и локальной обработки ошибок в контроллерах__ https://github.com/zmts/supra-api-nodejs/blob/master/core/lib/Server.js#L71 <details> <summary>Только в случае если нам как-то необходимо обработать/дополнить ошибку делаем так:</summary> ```js // IS GOOD testControllerHandler (req, res, next) { try { // my logic with throwed error await testService.getTest({ id: req.params.id }) } catch (error) { if (error.code === 'SPECIFIC_ERROR_CODE') { error.message = 'Lets describe our specific error' @@ -507,6 +516,21 @@ testControllerHandler (req, res, next) { } } ``` </details> <details> <summary>В остальных случаях хватит такого</summary> ```js // IS GOOD testControllerHandler (req) { // my logic with throwed error await testService.getTest({ id: req.params.id }) } ``` </details> ## Logging Вместо `console.log` используем логгеры под каждый тип сообщения (`trace`, `warn`, `error`, `fatal`). Логи пишем в `Sentry` или что-то подобное. Ошибки и security issues логируем в первую очередь, дальше все предупреждения и [трассирующие логи](https://en.wikipedia.org/wiki/Tracing_(software)) -
zmts revised this gist
Nov 25, 2019 . 1 changed file with 31 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -477,6 +477,37 @@ new ErrorWrapper({ ...errorCodes.ACCESS, message: 'Access denied dude !!!' }) ``` В последствии на фронте выводим ошибку из поля `message` или то что фронтенд посчтитает нужным в зависимости от поля `code`. ### Обработка ошибок Опишу частую ошибку встречающуюся во многих проектах на просторах интернета. Все ошибки обязаны быть перехваченны в единственном месте, в глобальном обработчике ошибок(глобальная `error middleware`). Остальные `unexpected` ошибки аля `unhandledRejection` в соотвествующих им хендлерам. https://github.com/zmts/supra-api-nodejs/blob/master/core/lib/Server.js#L71 __Это означает никаких try/catch и локальной обработки ошибок в контроллерах__ ```js testControllerHandler (req, res, next) { try { // my logic with throwed error await testService.getTest({ id }) } catch (error) { res.send(error) // это и есть локальная обработка ошибки } } ``` __Только в случае если нам как-то необходимо обработать/дополнить ошибку делаем так:__ ```js testControllerHandler (req, res, next) { try { // my logic with throwed error await testService.getTest({ id }) } catch (error) { if (error.code === 'SPECIFIC_ERROR_CODE') { error.message = 'Lets describe our specific error' } next(error) } } ``` ## Logging Вместо `console.log` используем логгеры под каждый тип сообщения (`trace`, `warn`, `error`, `fatal`). Логи пишем в `Sentry` или что-то подобное. Ошибки и security issues логируем в первую очередь, дальше все предупреждения и [трассирующие логи](https://en.wikipedia.org/wiki/Tracing_(software)) -
zmts revised this gist
Nov 12, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -22,7 +22,7 @@ code: https://github.com/zmts/supra-api-nodejs 3. Data layer - прослойка к БД ``` Controller(роутинг, проверка прав по роли) >> Action(проверка прав по id, схема валидации запроса, логика юзкейса) >> Data Layer(биндинги к БД) ``` ## Содержание -
zmts revised this gist
Nov 10, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -55,7 +55,7 @@ async function foo (id, options = {}) { `Assertion` это набор статических методов для базовой проверки типов аргуметров и возможно некоторых дополнительных опций (аля `{ required: true, notEmpty: true, positive: true }`). ## Config Все параметры неободимые для работы приложения должны хранится в едином конфиге. Параметры конфига делятся на два типа: констаннты и переменные. Первые храним как обычные поля, вторые извлекаем из переменных окружения (для примера это: логины/пароли доступа в БД, ключи для шифрования сессий или JWT токенов, ключи доступа к другим внешним ресурсам итд...). ```js class AppConfig extends BaseConfig { constructor () { -
zmts revised this gist
Nov 9, 2019 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -232,9 +232,6 @@ __Выходит такой Lifecycle >> Запрос проходит дефо Правила валидации (`validationRules`) импортируются из модели или в случае необходимости используются кастомные. ***Роль модели в экшене:*** Дабы не дублировать кучу подобных моделей(одна модель для создания сущности со всеми `required` полями, другая для обновления, третья для обновления конкретно указанного набора полей итд...) было принято решение не указывать в ф-ции валидации модели `required` требование. Валидаровать поля запроса в экшене помогает небольшой класс-хелпер `RequestRule` в его задачи входит предоставить [базовой ф-ции валидации](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L96) интересующие ее [аргументы](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L112) (ф-цию валидатор и `required` флаг). ```js @@ -291,7 +288,10 @@ module.exports = CreateAction ``` </details> Все эшнены являются __framework agnostic__ это значит что в экшенах отсуствует код относящийся к веб-фреймворку. Что позволеят нам переиспользовать экшены как в других проектах так и с другими фреймворками. __p.s.__ А еще разделение логики на отдельные экшены, облегчает командную работу над проектом. Меньше конфликтов при слиянии веток :) ## Model В данном подходе модель представляет собой исключительно набор полей и правил валидации без какой либо бизнес логики и доп. ф-ционала. -
zmts revised this gist
Nov 9, 2019 . 1 changed file with 3 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -232,6 +232,9 @@ __Выходит такой Lifecycle >> Запрос проходит дефо Правила валидации (`validationRules`) импортируются из модели или в случае необходимости используются кастомные. p.s. А еще разделение логики на отдельные экшены облегчает командную работу над проектом. Меньше конфликтов при слиянии веток :) ***Роль модели в экшене:*** Дабы не дублировать кучу подобных моделей(одна модель для создания сущности со всеми `required` полями, другая для обновления, третья для обновления конкретно указанного набора полей итд...) было принято решение не указывать в ф-ции валидации модели `required` требование. Валидаровать поля запроса в экшене помогает небольшой класс-хелпер `RequestRule` в его задачи входит предоставить [базовой ф-ции валидации](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L96) интересующие ее [аргументы](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L112) (ф-цию валидатор и `required` флаг). ```js -
zmts revised this gist
Nov 6, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -355,7 +355,7 @@ async function getById (id, options = {}) { ``` ## Clients `Clients` - это врапперы над внешними API/клиентами. Предназначение данной абстракции обеспечение единого контракта работы с внешними ресурсами. Допустим нам необходимо использовать в качестве хранилища файлов сервис от Амазон AWS S3. Мы создаем `S3Client` добавляем в него свои методы-обертки для работы с данным хранилищем. В случае критического изменения в клиенте амазона мы соотвественно меняем методы обертки без ущерба и переписывания остальной логики в остальных частях приложения. Единожды создав экземпляр клиент-враппера, можно переиспользовать его в разных местах вместо того что бы плодить инстансы голого клиента. <details> <summary>client example:</summary> -
zmts revised this gist
Nov 6, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -355,7 +355,7 @@ async function getById (id, options = {}) { ``` ## Clients `Clients` - это врапперы над внешними API/клиентами. Предназначение данной абстракции обеспечение единого контракта работы с внешними ресурсами. Допустим нам необходимо использовать в качестве хранилища файлов сервис от Амазон AWS S3. Мы создаем `S3Client` добавляем в него свои методы-обертки для работы с данным хранилищем. В случае критического изменения в клиенте амазона мы соотвественно меняем методы обертки без ущерба и переписывания остальной логики в остальных частях приложения. <details> <summary>client example:</summary> -
zmts revised this gist
Nov 6, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -233,7 +233,7 @@ __Выходит такой Lifecycle >> Запрос проходит дефо Правила валидации (`validationRules`) импортируются из модели или в случае необходимости используются кастомные. ***Роль модели в экшене:*** Дабы не дублировать кучу подобных моделей(одна модель для создания сущности со всеми `required` полями, другая для обновления, третья для обновления конкретно указанного набора полей итд...) было принято решение не указывать в ф-ции валидации модели `required` требование. Валидаровать поля запроса в экшене помогает небольшой класс-хелпер `RequestRule` в его задачи входит предоставить [базовой ф-ции валидации](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L96) интересующие ее [аргументы](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L112) (ф-цию валидатор и `required` флаг). ```js { validationRules: { -
zmts revised this gist
Nov 6, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -217,7 +217,7 @@ router.get('/api/users', function(req, res){ - Забирает результат вычисления и отправляет(`req.json(data)`) пользователю - В случе ошибки: собираются метаданные и передаются дальше для логирования __Выходит такой Lifecycle >> Запрос проходит дефолтные мидлвари >> Попадает в контроллер >> Попадает в `actionRunner` тот проверяет права доступа текущего юзера к экшену, валидирует запрос и стартует процесс обработки (статическая ф-ция `run`) >> В экшене выполняется бизнес логика и результат возращается в контроллер >> `actionRunner` получает результат и возвращает его клиенту.__ Что не стоит делать в контроллере: - Валидировать параметры(`req.params`). Как показала практика это плохая идея. Проверку параметров лучше стоит делать непосредственно в экшене. Таким образом в дальнейшем будет более наглядно видно какие парметры в запросе доступны экшену. -
zmts revised this gist
Nov 6, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -17,7 +17,7 @@ code: https://github.com/zmts/supra-api-nodejs #### Несколько слов про разделение приложения на слои: Обычно в слое Контроллеров данные из запроса валидируются и приводятся к виду необходимому для последующего сервисного слоя. В свою очередь сервисный слой полностью изолирует бизнес логику и возвращает результат вычислений в контроллер. Или иначе ? Как вы считаете ? И так каждый программист делает как считает нужно либо так как привык. Давайте будем откровенны. Приводило ли подобная архитектура к тому ради чего задумывалась(к порядку и разделению логики) ? Зачастую все эти попытки сводятся к `big ball of mud`. Зачастую в контроллер просачивается бизнес логика, а в сервисный слой валидация, а то и вообще контекст запроса. Дабы так не происходило данный подход предлагает использовать единый слой для всей логики касаемо конкретного `use case`. Назовем этот слой `Action layer`. В итоге имеем: 1. Минималистичный контроллер - сугубо маппинг экшенов на роуты 2. Слой экшенов отвечающий за валидацию, всевозможные проверки доступа(владелец/не владелец/админ/не админ/аноним...) и бизнес логику 3. Data layer - прослойка к БД -
zmts revised this gist
Nov 6, 2019 . 1 changed file with 4 additions and 10 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,16 +16,10 @@ code: https://github.com/zmts/supra-api-nodejs #### Несколько слов про разделение приложения на слои: Обычно в слое Контроллеров данные из запроса валидируются и приводятся к виду необходимому для последующего сервисного слоя. В свою очередь сервисный слой полностью изолирует бизнес логику и возвращает результат вычислений в контроллер. Или иначе ? Как вы считаете ? И так каждый программист делает как считает нужно либо так как привык. Давайте будем откровенны. Приводило ли подобная архитектура к тому ради чего задумывалась(к порядку и разделению логики) ? Зачастую все эти попытки сводятся к `big ball of mud`. Зачастую в контроллер просачивается бизнес логика, а в сервисный слой валидация, а то и вообще контекст запроса. Дабы так не происходило данный подход предлагает использовать единый слой для всей логики касаемо конкретного `use case`. Назовем этот слой `Action layer`. В итоге имеем: 1. Минималистичный контроллер - сугубо маппинг экшенов на роуты. 2. Слой экшенов отвечающий за валидацию, всевозможные проверки доступа(владелец/не владелец/админ/не админ/аноним...) и бизнес логику 3. Data layer - прослойка к БД ``` Controller(роутинг) >> Action(проверка прав, схема валидации запроса, логика юзкейса) >> Data Layer(биндинги к БД) -
zmts revised this gist
Oct 28, 2019 . 1 changed file with 0 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -621,5 +621,3 @@ function foo (firstRequired, secondRequired, { someOptionalParam = false } = {}) Видео Виктора Турского про архитектуру - https://www.youtube.com/watch?v=Z08xL-oXMh0 (2017) - https://www.youtube.com/watch?v=TjvIEgBCxZo (2019) -
zmts revised this gist
Oct 24, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -41,7 +41,7 @@ Controller(роутинг) >> Action(проверка прав, схема ва - [Model](#model) - [Clients](#clients) - [Providers](#providers) - [Helpers](#authhelpers) - [DAO](#dao) - [Errors](#errors) - [Logging](#logging) -
zmts revised this gist
Oct 24, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -446,7 +446,7 @@ module.exports = new RootProvider() ``` </details> ## Auth(helpers) Хелперы отвечают за всевозможный процессинговые и утилитарные ф-ции(шифрование, JWT) В своем большинстве реализованные через промис. ## DAO -
zmts revised this gist
Oct 24, 2019 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -239,12 +239,12 @@ __Выходит такой Lifecycle >> Запрос попадает в кон Правила валидации (`validationRules`) импортируются из модели или в случае необходимости используются кастомные. ***Роль модели в экшене:*** Дабы не дублировать кучу подобных моделей(одна модель для создания сущности со всеми `required` полями другая для обновления, третья для обновления конкретно указанного набора полей итд...) было принято решение не указывать в ф-ции валидации модели `required` требование. Валидаровать поля запроса в экшене помогает небольшой класс-хелпер `RequestRule` в его задачи входит предоставить [базовой ф-ции валидации](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L96) интересующие ее [аргументы](https://github.com/zmts/supra-api-nodejs/blob/master/controllers/BaseController.js#L112) (ф-цию валидатор и `required` флаг). ```js { validationRules: { body: { id: new RequestRule(PostModel.schema.id, { required: true }) } } } -
zmts revised this gist
Oct 24, 2019 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -174,12 +174,12 @@ actionRunner (action) { <summary>Таким образом контроллер представляет собой совсем небольшую прослойку мапинга экшенов на роуты</summary> ```js const router = require('express').Router() const actions = require('../actions/posts') const BaseController = require('../core/BaseController') const ErrorWrapper = require('../core/ErrorWrapper') const { errorCodes } = require('../config') const logger = require('../logger') class PostsController extends BaseController { get router () { @@ -195,7 +195,7 @@ class PostsController extends BaseController { } async init () { logger.info(`${this.constructor.name} initialized...`) } } -
zmts revised this gist
Sep 10, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -44,7 +44,7 @@ Controller(роутинг) >> Action(проверка прав, схема ва - [Helpers](#helpers) - [DAO](#dao) - [Errors](#errors) - [Logging](#logging) - [Policy/Roles/Permissions](#policyrolespermissions) ## Assertion(type checking) -
zmts revised this gist
Sep 10, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -228,7 +228,7 @@ __Выходит такой Lifecycle >> Запрос попадает в кон Что не стоит делать в контроллере: - Валидировать параметры(`req.params`). Как показала практика это плохая идея. Проверку параметров лучше стоит делать непосредственно в экшене. Таким образом в дальнейшем будет более наглядно видно какие парметры в запросе доступны экшену. ## Actions Основным ключевым моментом является использование отдельного класса для каждого эндпоинта. Я определяю понятие `Action` как класс инкапсулирующий всю логику работы эндпоинта. То есть для реализации круда у нас будет 5 файлов (`CreatePostAction`, `GetPostByIdAction`, `UpdatePostAction`, `DeletePostAction`, `ListPostsAction`) по экшену на каждый эндпоинт. Каждый экшен обязан имплементировать такой контракт:
NewerOlder