Создание бизнес-логики JavaScript с возможностью повторного использования с peasy-js было рецензировано Стефаном Максом . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
При написании приложений мы часто соединяем нашу ценную бизнес-логику с кодом, специфичным для фреймворка. Например, при использовании Angular распространена бизнес-логика между службами, контроллерами и даже директивами.
Это также относится к JavaScript, написанному для серверной части, где обычно помечают наши действия контроллера Sails (например) бизнес-логикой, которая напрямую использует нашу логику доступа к данным через ORM / ODM, такие как Mongoose , и другие сквозные проблемы.
Эта связь обычно приводит к коду, который трудно повторно использовать, масштабировать, тестировать, а также адаптировать или переходить на новые технологии.
В этой статье я покажу вам, как использовать библиотеку peasy-js, чтобы помочь структурировать бизнес-логику таким образом, чтобы ее можно было многократно использовать между передней и внутренней частями приложения и легко переносить между различными рамки.
Раскрытие : я автор peasy-js
Должны ли мы перестать использовать фреймворки?
Наоборот, я считаю, что эти платформы предлагают огромные преимущества как на клиенте, так и на сервере. Однако я предлагаю абстрагировать нашу бизнес-логику в составные блоки, создавая код, полностью независимый от своих потребителей.
Компонентируя нашу бизнес-логику, мы можем легко тестировать, заменять, перегруппировать, повторно использовать и использовать эти компоненты в любой архитектуре приложения, используя любой клиент JavaScript, сервер, технологии доступа к данным и воображаемую инфраструктуру.
Отделите свою бизнес-логику
peasy-js — это инфраструктура среднего уровня, которая упрощает причудливую замену инфраструктур пользовательского интерфейса, серверной части и доступа к данным в наших приложениях, создавая бизнес-логику составным, повторно используемым, масштабируемым и тестируемым образом. Другими словами, peasy-js предлагает руководство по абстрагированию нашей бизнес-логики в составные блоки путем создания кода, который разделяет разделение задач (SoC) .
Рамочная усталость
Подожди, пока не уходи!
Я знаю, что вы думаете, «тьфу, еще один каркас?». Да, peasy-js — это действительно микрофрейм. Тем не менее, есть вероятность, что если мы пойдем по пути компонентизации нашей бизнес-логики, мы все равно в конечном итоге создадим нашу собственную микро-среду.
Бесчисленные часы были потрачены на проектирование, разработку и тестирование peasy-js, поддерживая практически любой рабочий процесс, какой только можно вообразить. Надеюсь, что с небольшими препятствиями для входа небольшие инвестиции в обучение стоят вашего времени.
Однако, если вы обнаружите, что peasy-js не совсем для вас, надеюсь, вы получите некоторое представление о том, как вы можете реализовать свой собственный бизнес-уровень, используя некоторые шаблоны в платформе.
Основные понятия
Давайте посмотрим, что peasy-js предлагает нам:
- Простая в использовании и гибкая система правил ведения бизнеса и проверки
- Масштабируемость и возможность повторного использования (отделяет бизнес-логику и логику проверки от использования кода и сред)
- Легкая тестируемость
Peasy-JS включает в себя четыре основных понятия. Каждый из них описан ниже с кратким описанием и будет более подробно рассмотрен в статье.
Бизнес Сервис
Реализация BusinessService представляет объект (например, пользователей или проекты) и отвечает за раскрытие бизнес-функций с помощью команд. Эти команды инкапсулируют CRUD и другие связанные с бизнесом функции.
команда
Команда отвечает за управление выполнением логики инициализации, проверки и выполнения бизнес-правил, а также другой логики (вызовы прокси данных, логика рабочего процесса и т. Д.), Соответственно, через конвейер выполнения команды .
правило
Правило может быть создано для представления правила проверки (длина поля или обязательно) или бизнес-правила (авторизация, обоснованность цены и т. Д.). Правила используются командами и могут быть объединены в цепочку, сконфигурированы для выполнения на основе выполнения предыдущего правила и т. Д. Правила также могут быть сконфигурированы для запуска кода на основе результата их выполнения.
DataProxy
DataProxy отвечает за хранение и извлечение данных и служит уровнем абстракции для хранилищ данных, которые охватывают (но не ограничиваются) следующее:
- Реляционные базы данных — SQLite, MySQL, Oracle, SQL Server и др.
- Базы данных документов (NoSQL) — MongoDB, VelocityDB и т. Д.
- Сервисы — HTTP, SOAP и др.
- Кеш-магазины — Redis, Azure и др.
- Очереди — RabbitMQ, MSMQ и т. Д.
- Файловая система
- Хранилища данных в памяти для тестирования
Примеры: Peasy-js в действии
Примечание: простой пример браузера можно посмотреть на plnkr, который охватывает все, что обсуждается в этом разделе.
Вот пример того, как может выглядеть бизнес-логика, написанная с помощью peasy-js в службе Angular на клиенте:
Рисунок А
var dataProxy = new CustomerHttpDataProxy(); var service = new CustomerService(dataProxy); var customer = { name: "Frank Zappa", birthDate: new Date('12/21/1940') }; var command = service.insertCommand(customer); command.execute(function(err, result) { if (result.success) { customer = result.value; } else { console.log(result.errors); } });
Теперь давайте рассмотрим пример того, как может выглядеть та же бизнес-логика в контроллере Express.js на сервере:
Рисунок Б
var dataProxy = new CustomerMongoDataProxy(); var service = new CustomerService(dataProxy); var customer = { name: "Frank Zappa", birthDate: new Date('12/21/1940') }; var command = service.insertCommand(customer); command.execute(function(err, result) { if (result.success) { customer = result.value; } else { console.log(result.errors); } });
Заметили разницу? Прекрасная вещь заключается в том, что нет никакой разницы, за исключением разных прокси данных, вводимых в бизнес-сервис в каждом образце.
Помните, что прокси-сервер данных является нашей абстракцией доступа к данным и может представлять конкретную реализацию доступа к файловой системе, базе данных, очереди, кэш-памяти, в памяти и HTTP-коммуникациям.
Эта абстракция позволяет нам обмениваться прокси-серверами данных на основе требуемых системных архитектур и конфигураций, одновременно применяя SoC и предоставляя возможность многократного использования в кодовых базах и облегчая тестирование. Что может быть не сразу очевидно, так это то, что этот подход всегда подвергает наши полезные нагрузки одной и той же бизнес-логике, независимо от источника или назначения наших данных. Это все скоро раскроется.
С точки зрения потребления, это действительно все, что нужно сделать. Использование нашей бизнес-логики, разработанной с помощью peasy-js, представит узнаваемую тему, независимо от нашей архитектуры и технологий, которые ее потребляют.
Говоря об архитектуре, давайте обратим наше внимание на потенциальную архитектуру, которая становится легко достижимой при разработке нашей бизнес-логики таким образом, в то же время исследуя участников peasy-js немного глубже:
Слева направо мы видим, что клиентское приложение использует такую среду, как Angular, React, Backbone и т. Д. Чтобы достичь максимальной масштабируемости, обратите внимание, что мы можем переместить реализацию бизнес-логики из реализаций участников инфраструктуры пользовательского интерфейса (служб, контроллеров и т. Д.). .) в свою собственную компонентную кодовую базу или промежуточный уровень.
Далее обратите внимание, что промежуточный уровень связывается с веб-сервером. Это стало возможным благодаря наличию прокси данных. Как показано на рисунке A, служба Angular, потребляющая нашу бизнес-логику, создает экземпляр CustomerHttpDataProxy
. В результате, когда команда вставки выполняется, она подвергает предоставленную полезную нагрузку любым настроенным бизнес-правилам. В случае успешной проверки будет вызвана соответствующая функция insert
нашего прокси-сервера данных, и, соответственно, будет выдан POST-запрос к настроенной конечной точке клиента.
И наоборот, обратите внимание, что та же бизнес-логика, которая используется в нашем интерфейсе, также используется нашим приложением node.js. Как показано на рисунке B, экспресс-контроллер, использующий нашу бизнес-логику, создает экземпляр CustomerMongoDataProxy
. Однако на этот раз при выполнении команды insert
соответствующая функция insert
нашего прокси-сервера данных выполнит INSERT для нашей базы данных, используя API MongoDB или ORD, например Mongoose.
Наконец, поскольку наши реализации прокси-серверов данных придерживаются одного и того же интерфейса, мы можем внедрить их в наши бизнес-сервисы в зависимости от того, как мы хотим развернуть наше приложение. На схеме бизнес-сервисы используют прокси-серверы данных, которые взаимодействуют с HTTP-сервисами на клиенте. Однако, как только запрос обрабатывается веб-API, те же бизнес-сервисы, размещенные в Node.js, внедряются с прокси данных, которые взаимодействуют с базой данных, очередью, кешем, файловой системой и т. Д.
Теперь, когда мы понимаем участников peasy-js с высокого уровня и некоторых преимуществ, которые они предоставляют, давайте рассмотрим примеры их реализации.
CustomerHttpDataProxy
var CustomerHttpDataProxy = function() { var request = require('request'); return { insert: insert }; function insert(data, done) { request({ method: 'POST', url: 'http://localhost:3000/customers', body: data, json = true }, function (error, response, body) { done(error, body); } ); }; };
CustomerMongoDataProxy
var CustomerMongoDataProxy = function() { var connectionString = 'mongodb://localhost:12345/orderEntry'; var mongodb = require('mongodb').MongoClient; return { insert: insert }; function insert(data, done) { mongodb.connect(connectionString, function(err, db) { if (err) { return done(err); } var collection = db.collection('customers'); collection.insert(data, function(err, data) { db.close(); done(err, data); }); }); }; };
В этих примерах кода прокси данных обратите внимание, что они придерживаются того же интерфейса, но абстрагируют логику реализации. Это то, что позволяет нам масштабировать наше приложение. Поменяв прокси данных, мы можем видеть, что теперь у нас есть действительно многократно используемый средний уровень, который полностью независим от любого потребляющего кода (клиента или сервера). Эта концепция проектирования прокси-серверов данных действительно является ключом к достижению масштабируемости и легкой тестируемости.
Наконец, обратите внимание, что для краткости мы определили только функцию вставки в наших прокси данных. Однако в реальной производственной среде мы, скорее всего, представим все операции CRUD и, возможно, еще несколько. Вы можете увидеть полную реализацию CustomerMongoDataProxy здесь .
Обслуживание клиентов
var CustomerService = BusinessService.extend({ functions: { _onInsertCommandInitialization: function(context, done) { var customer = this.data; utils.stripAllFieldsFrom(customer).except(['name', 'address']); utils.stripAllFieldsFrom(customer.address).except(['street', 'zip']); done(); } } }).service;
В этом примере мы предоставили логику инициализации для открытой вставки CustomerService insertCommand, которая вносит белые списки в поля перед вызовом функции insert
нашего прокси-сервера данных. Каждая операция CRUD по умолчанию, предоставляемая через наши реализации бизнес-сервисов, предоставляет обработчики событий, связанные с каждой командой. Эти методы можно посмотреть здесь .
Обратите внимание, что мы используем статическую функцию BusinessService.extend
, которая создает функцию конструктора, предоставляемую через член службы возвращаемого объекта. Вы также можете свободно использовать наследование ES6 или прототипирование, если вам удобнее использовать эти подходы. Образцы обоих можно найти здесь .
Теперь, когда мы определили логику инициализации для insertCommand
нашего бизнес-сервиса, давайте создадим пару правил и insertCommand
их соответствующим образом:
NameRule
var NameRule = Rule.extend({ association: "name", params: ['name'], functions: { _onValidate: function(done) { if (this.name === "Jimi") { this._invalidate("Name cannot be Jimi"); } done(); } } });
AgeRule
var AgeRule = Rule.extend({ association: "age", params: ['birthdate'], functions: { _onValidate: function(done) { if (new Date().getFullYear() - this.birthdate.getFullYear() < 50) { this._invalidate("You are too young"); } done(); } } });
Обратите внимание, что мы используем статический метод Rule.extend
в обоих примерах кода, который создает для нас функцию конструктора. Как и раньше, вы также можете использовать ES6 или прототип наследования (примеры здесь ).
Теперь давайте подключим их в нашей CustomerService:
Проводить наши правила
var CustomerService = BusinessService.extend({ functions: { _onInsertCommandInitialization: function(context, done) { var customer = this.data; utils.stripAllFieldsFrom(customer).except(['name', 'address']); utils.stripAllFieldsFrom(customer.address).except(['street', 'zip']); done(); }, _getRulesForInsertCommand: function(context, done) { var customer = this.data; done(null, [ new NameRule("name", customer.name), new AgeRule("age", customer.birthDate) ]); } } }).service;
В последнем фрагменте кода мы связали наши правила в нашей бизнес-службе и внедрили их в конвейер выполнения команд вставки. Мы сделали это, предоставив реализацию для функции _getRulesForInsertCommand()
.
В этом примере мы настроили оба правила для выполнения независимо от результатов друг друга. Например, если проверка NameRule завершится неудачно, AgeRule все равно будет оценен, и наоборот.
Что хорошо в правилах peasy-js, так это то, что они чрезвычайно гибки и могут быть написаны и настроены для поддержки практически любого возможного сценария. Например, мы можем связать выполнение правил таким образом, что AgeRule будет выполняться только в том случае, если проверка NameRule завершится успешно, и наоборот. Это чрезвычайно полезно, когда нашим правилам необходимо получать данные из хранилища данных (потенциально дорогой хит).
Более подробную информацию о правилах можно найти в документации .
Тестирование нашей бизнес-логики
Поскольку peasy-js придерживается принципов программирования SOLID , становится очень легко тестировать наши бизнес-сервисы, команды и правила.
Давайте посмотрим, как мы можем легко протестировать наше NameRule
:
it("fails when the supplied name is Jimi", () => { var rule = new NameRule("Jimi"); rule.validate(() => { expect(rule.valid).toBe(false); expect(rule.association).toEqual("name"); }); }); it("succeeds when the supplied name is not Jimi", () => { var rule = new NameRule("James"); rule.validate(() => { expect(rule.valid).toBe(true); }); });
Сохраняя наши правила простыми и сфокусированными, их не только легко использовать, но и чрезвычайно легко тестировать. Это также относится к тестированию наших бизнес-сервисов и пользовательских команд.
Тестирование само по себе является большой темой, поэтому это хорошая конечная точка для статьи. Просто отметьте, что тестировать нашу бизнес-логику с помощью peasy-js чрезвычайно легко, и здесь можно найти множество тестовых образцов.
Хотите узнать больше?
Доступно полное приложение-образец для ввода заказов / управления запасами, которое демонстрирует средний уровень, написанный на peasy-js. Бизнес-логика используется приложением Express.js, размещенным в Node.js, которое предоставляет веб-API. Образец прост в запуске и сопровождается документацией, которая поможет вам приступить к работе в считанные минуты.
peasy-js побуждает нас писать нашу бизнес-логику, которая четко отделена от каркасов, которые мы используем. Побочным эффектом этого является то, что он позволяет легко развертывать наш код множеством способов. Наконец, это делает почти тривиальным переход на новые системы или принятие новых систем по мере их старения.
Следуете ли вы аналогичным шаблонам в ваших приложениях? Как вы думаете, использование чего-то вроде peasy-js поможет вам написать лучший код? Дайте мне знать, что вы думаете ниже в комментариях!