व्यवहार में क्रॉस-प्लेटफॉर्म कॉमनजेएस



हम किस बारे में बात कर रहे हैं?



जेएस मॉड्यूल के बारे में जो ब्राउज़र और सर्वर में उपयोग किया जा सकता है। उनकी बातचीत और बाहरी निर्भरता के बारे में। कम सिद्धांत, अधिक अभ्यास। युवा सेनानी पाठ्यक्रम के भाग के रूप में, हम Node.JS: ToDo-list के आधार पर एक सरल और उच्च मूल एप्लिकेशन लागू कर रहे हैं। इसके लिए हमें निम्न करना होगा:

  1. "एक्सप्रेस ढांचे के आधार पर" क्रॉस-प्लेटफ़ॉर्म मॉड्यूल प्राप्त करें;
  2. उन्हें मंच-निर्भर सहयोगियों के साथ काम करने के तरीके सिखाने के लिए;
  3. क्लाइंट और सर्वर के बीच एक ट्रांसपोर्ट लेयर बनाएं;
  4. ToDo सूची ले लो;
  5. परिणाम को समझने के लिए।




आवेदन आवश्यकताओं



हम पूरे उपक्रम के सार पर ध्यान केंद्रित करते हैं और कार्यान्वयन के लिए न्यूनतम कार्यक्षमता लेते हैं। हम निम्नानुसार आवश्यकताओं को तैयार करते हैं:

  1. आवेदन एक ब्राउज़र के माध्यम से सुलभ है;
  2. उपयोगकर्ता एक सत्र में अपनी ToDo-list के साथ काम करता है। जब आप पृष्ठ को फिर से लोड करते हैं, तो टैब या ब्राउज़र को बंद करने के बाद, सूची को बचाया जाना चाहिए - एक नया बनाएं;
  3. उपयोगकर्ता सूची में नए आइटम जोड़ सकता है;
  4. उपयोगकर्ता जोड़े गए आइटम को पूर्ण के रूप में चिह्नित कर सकता है।




फ्रेम बना लें



समस्याओं के बिना, हम एक्सप्रेस ढांचे के आधार पर आवेदन के ढांचे को बढ़ाते हैं। हम बॉक्स से मिली संरचना को थोड़ा संशोधित करेंगे:

. ├── bin ├── client //     ,   ├── modules //  , ,  CommonJS  ├── public │  └── stylesheets ├── routes └── views
      
      







आइए विषय क्षेत्र से हमारा पहला मॉड्यूल बनाएं - प्वाइंट, टूडो सूची के निर्माता:

 // modules/Point/Point.js /** *    * @param {Object} params * @param {String} params.description * @param {String} [params.id] * @param {Boolean} [params.isChecked] * @constructor */ function Point(params) { if (!params.description) { throw 'Invalid argument'; } this._id = params.id; this._description = params.description; this._isChecked = Boolean(params.isChecked); } Point.prototype.toJSON = function () { return { id: this._id, description: this._description, isChecked: this._isChecked }; }
      
      





पूरी तरह से
 /** * @param {String} id */ Point.prototype.setId = function (id) { if (!id) { throw 'Invalid argument'; } this._id = id; } /** * @returns {String} */ Point.prototype.getId = function () { return this._id; } Point.prototype.check = function () { this._isChecked = true; } Point.prototype.uncheck = function () { this._isChecked = false; } /** * @returns {Boolean} */ Point.prototype.getIsChecked = function () { return this._isChecked; } /** * @returns {String} */ Point.prototype.getDescription = function () { return this._description; } module.exports = Point;
      
      







अद्भुत। यह हमारा पहला क्रॉस-प्लेटफॉर्म मॉड्यूल है और हम पहले से ही सर्वर पर इसका उपयोग कर सकते हैं, उदाहरण के लिए, इस तरह:

 // routes/index.js var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function (req, res) { var Point = require('../modules/Point'); var newPoint = new Point({ description: 'Do something' }); console.log('My new point:', newPoint); }); module.exports = router;
      
      





एक ब्राउज़र में कॉमनजस मॉड्यूल के साथ काम करना सुनिश्चित करने के कई तरीके हैं, एक्सप्रेस ब्राउजर-मिडलवेयर के लिए मेरे लिए मिडलवेयर को कॉन्फ़िगर करना और उपयोग करना सबसे सरल है:

 // app.js // ... var browserify = require('browserify-middleware'); app.use('/client', browserify('./client')); // ...
      
      





इस तरह के सरल कोड को जोड़कर, हम तुरंत अपने क्लाइंट एप्लिकेशन की पहली पंक्तियों को लिख सकते हैं:

 // client/todo.js var console = require('console'); //  `node_modules/browserify/node_modules/console-browserify` var Point = require('../modules/Point');
      
      







Browserify, Nodian मॉड्यूल लोडिंग एल्गोरिथ्म का उपयोग करता है और कोर लाइब्रेरीज़ के ब्राउज़र-आधारित कार्यान्वयन भी प्रदान करता है । इस बारे में पहले से ही बहुत कुछ लिखा जा चुका है, इसलिए मैं केवल इतना कह सकता हूं कि अब /client/todo.js पर डाउनलोड की गई स्क्रिप्ट ब्राउज़र में पूरी तरह से चालू है।



मॉड्यूल के बारे में बात करते हैं



मेरी परियोजना में, मैंने मॉड्यूल के निम्नलिखित सशर्त विभाजन का उपयोग किया:



उपयोगिता मॉड्यूल

उनकी मदद से, डेवलपर कोड को व्यवस्थित और बनाए रखता है। उदाहरण के लिए, हमारे मामले में, यह वादों की लाइब्रेरी है, लॉश , कंसोल। अधिकांश भाग के लिए, ऐसे मॉड्यूल न केवल क्रॉस-प्लेटफ़ॉर्म हैं, बल्कि कई बूट प्रारूपों (कॉमनजेस, एएमडी) का भी समर्थन करते हैं।



डोमेन मॉड्यूल

डोमेन ऑब्जेक्ट के साथ काम करने के लिए एक इंटरफ़ेस प्रदान करें। हमने पहले से ही एक ऐसा मॉड्यूल बनाया है - प्वाइंट कंस्ट्रक्टर, जल्द ही सूची मॉड्यूल दिखाई देगा, जिसमें हमें वह इंटरफ़ेस प्रदान करना होगा जो हमें चाहिए (AddPoint, getPoints, checkPoint) और उपयोगकर्ता मॉड्यूल, जो उपयोगकर्ता सत्र को प्रारंभ करने के लिए जिम्मेदार है।



इस तरह के मॉड्यूल दोनों पूरी तरह से क्रॉस-प्लेटफॉर्म हो सकते हैं और प्लेटफॉर्म-डिपेंडेंट पार्ट्स होते हैं। उदाहरण के लिए, कुछ तरीके या गुण ब्राउज़र में उपलब्ध नहीं होने चाहिए। लेकिन अक्सर प्लेटफ़ॉर्म-निर्भर भाग मॉड्यूल की निम्न श्रेणी में आता है।



दाल मॉड्यूल (डेटा एक्सेस लेयर)

ये स्रोतों के एक मनमाने सेट से डेटा तक पहुँचने और उन्हें एक आंतरिक प्रतिनिधित्व (ऑब्जेक्ट्स, संग्रह) और इसके विपरीत में परिवर्तित करने के लिए जिम्मेदार मॉड्यूल हैं। एक ब्राउज़र के लिए, यह स्थानीयस्टोरेज, सेशनस्टोरेज, कुकीज, एक बाहरी एपीआई हो सकता है। सर्वर पर और भी अधिक विकल्प हैं: डेटाबेस की एक पूरी श्रृंखला, एक फाइल सिस्टम और, फिर से, कुछ बाहरी एपीआई।



यदि विषय क्षेत्र का क्रॉस-प्लेटफ़ॉर्म मॉड्यूल डीएएल के साथ बातचीत करता है, तो डीएएल मॉड्यूल में एक एकल इंटरफ़ेस के साथ एक ब्राउज़र और सर्वर कार्यान्वयन होना चाहिए। तकनीकी रूप से, हम इसे उपयोगी ब्राउजर फीचर का उपयोग करके व्यवस्थित कर सकते हैं , जिसमें पैकेज.जसन मॉड्यूल में ब्राउज़र की संपत्ति को निर्दिष्ट करना शामिल है । इस प्रकार, डोमेन मॉड्यूल निष्पादन पर्यावरण के आधार पर विभिन्न डीएएल मॉड्यूल के साथ काम कर सकते हैं:

 { "name" : "dal", "main" : "./node.js", //     "browser": "./browser.js" //   browserify     }
      
      







हम मॉड्यूल लागू करते हैं



हमारे कार्य के लिए कौन से मॉड्यूल की आवश्यकता होगी? मेमकाचे को सर्वर पर भंडारण के रूप में कार्य करने दें, इसमें हम अपने टूडू-सूचियों को संग्रहीत करेंगे। उपयोगकर्ता की पहचान ब्राउज़र में होगी, हम सत्र पहचानकर्ता को सेशनस्टोरेज में डालेंगे और हम प्रत्येक अनुरोध को सर्वर तक पहुंचाएंगे। तदनुसार, सर्वर पर हमें अनुरोध पैरामीटर से इस पहचानकर्ता को चुनना होगा।



यह पता चला है कि डीएएल स्तर पर हमें सेशनस्टोरेज और मेम्चे से बातचीत के लिए एक प्रोटोकॉल लागू करना चाहिए (हम मानक एक्सप्रेस टूल का उपयोग करके अनुरोध पैरामीटर प्राप्त करते हैं)।

मॉड्यूल / दाल / ब्राउज़र / sessionStorage.js
 module.exports.set = function () { sessionStorage.setItem.apply(sessionStorage, arguments); } module.exports.get = function () { return sessionStorage.getItem.apply(sessionStorage, arguments); }
      
      







मॉड्यूल / दाल / नोड / memcache.js
 var vow = require('vow'); var _ = require('lodash'); var memcache = require('memcache'); var client = new memcache.Client(21201, 'localhost'); var clientDefer = new vow.Promise(function(resolve, reject) { client .on('connect', resolve) .on('close', reject) .on('timeout', reject) .on('error', reject) .connect(); }); /** *    Memcache * @see {@link https://github.com/elbart/node-memcache#usage} * @param {String} clientMethod * @param {String} key * @param {*} [value] * @returns {vow.Promise} resolve with {String} */ function request(clientMethod, key, value) { var requestParams = [key]; if (!_.isUndefined(value)) { requestParams.push(value); } return new vow.Promise(function (resolve, reject) { requestParams.push(function (err, data) { if (err) { reject(err); } else { resolve(data); } }); clientDefer.then(function () { client[clientMethod].apply(client, requestParams); }, reject); }); } /** *     * @param {String} key * @param {*} value * @returns {vow.Promise} */ module.exports.set = function (key, value) { return request('set', key, value); } /** *     * @param {String } key * @returns {vow.Promise} resolve with {String} */ module.exports.get = function (key) { return request('get', key); }
      
      







अब हम उपयोगकर्ता डोमेन के निम्नलिखित मॉड्यूल को लागू कर सकते हैं, जो हमें एक एकल गेटआईड विधि के साथ एक वस्तु प्रदान करेगा:

मॉड्यूल / उपयोगकर्ता / दाल / browser.js
 var storage = require('../../dal/browser/sessionStorage'); var key = 'todo_user_id'; /** *   id * @returns {String} */ function makeId() { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var i; for (i = 0; i < 10; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } module.exports = { /** * @returns {String} */ getId: function () { var userId = storage.get(key); if (!userId) { userId = makeId(); storage.set(key, userId); } return userId; } };
      
      







मॉड्यूल / उपयोगकर्ता / दाल / नोड.जेएस
 var app = require('../../../app'); module.exports = { /** * @returns {String} */ getId: function () { return app.get('userId'); //     middleware } };
      
      







मॉड्यूल / उपयोगकर्ता / दाल / package.json
 { "name" : "dal", "main" : "./node.js", "browser": "./browser.js" }
      
      







 // modules/user/user.js var dal = require('./dal'); //     ./dal/browser.js,   - ./dal/node.js function User() { } /** *    * @returns {String} */ User.prototype.getId = function () { return dal.getId(); } module.exports = new User();
      
      







हम REST प्रोटोकॉल के आधार पर ब्राउज़र और सर्वर के बीच इंटरैक्शन को व्यवस्थित करते हैं, जिससे हमें ब्राउज़र के DAL स्तर पर इसे लागू करने की आवश्यकता होगी:

मॉड्यूल / दाल / ब्राउज़र / rest.js
 var vow = require('vow'); var _ = require('lodash'); /** *    REST API * @param {String} moduleName -   * @param {String} methodName -   * @param {Object} params -   * @param {String} method -   * @returns {vow.Promise} resolve with {Object} xhr.response */ module.exports.request = function (moduleName, methodName, params, method) { var url = '/api/' + moduleName + '/' + methodName + '/?', paramsData = null; if (_.isObject(params)) { paramsData = _.map(params, function (param, paramName) { return paramName + '=' + encodeURIComponent(param); }).join('&'); } if (method !== 'POST' && paramsData) { url += paramsData; paramsData = null; } return new vow.Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.responseType = 'json'; xhr.onload = function() { if(xhr.status === 200) { resolve(xhr.response); } else { reject(xhr.response || xhr.statusText); } }; xhr.send(paramsData); }); }
      
      







और एक विशेष एक्सप्रेस राउटर जो हमारे डोमेन मॉड्यूल के साथ काम करेगा:

 // routes/api.js // ... router.use('/:module/:method', function (req, res) { var module = require('../modules/' + req.params.module), method = module[req.params.method]; if (!method) { res.send(405); return; } method.apply(module, req.apiParams) .then(function (data) { res.json(data); }, function (err) { res.send(400, JSON.stringify(err)); }); }); // ...
      
      





कार्य की शर्तों के आधार पर, हमें एपीआई में निम्नलिखित तरीके प्रदान करने होंगे:

  1. GET, / list / getPoints - वर्तमान उपयोगकर्ता की ToDo- सूची में एक टू-डू सूची प्राप्त करें;
  2. POST, / list / addPoint - वर्तमान उपयोगकर्ता की ToDo- सूची में एक नया आइटम प्राप्त करें;
  3. पोस्ट, सूची / चेकपॉइंट - आइटम को चिह्नित करें जैसा कि किया गया है;


एक नया आइटम जोड़ने के मामले में, हमें राउटर को अतिरिक्त जिम्मेदारियां सौंपनी होंगी: मॉड्यूल में ट्रांसमिशन के लिए अनुरोध मापदंडों को आंतरिक प्रतिनिधित्व में परिवर्तित करना:

 router.post('/list/addPoint', function (req, res, next) { var Point = require('../modules/Point'), point; req.apiParams = []; try { point = new Point(JSON.parse(req.param('point'))); req.apiParams.push(point); } catch (e) {} next(); });
      
      





ठीक है, अब हम सूची विषय क्षेत्र के अंतिम मॉड्यूल को लागू कर सकते हैं:

मॉड्यूल / सूची / दाल / browser.js
 var _ = require('lodash'); var rest = require('../../dal/browser/rest'); var Point = require('../../Point'); module.exports = { /** * @param {User} user * @returns {vow.Promise} resolve with {Point[]} */ getPoints: function (user) { return rest.request('list', 'getPoints', {userId: user.getId()}, 'GET') .then(function (points) { return _.map(points, function (point) { return new Point(point); }); }); }, /** * @param {User} user * @param {Point} point * @returns {vow.Promise} resolve with {Point} */ addPoint: function (user, point) { var requestParams = { userId: user.getId(), point: JSON.stringify(point) }; return rest.request('list', 'addPoint', requestParams, 'POST') .then(function (point) { return new Point(point); }); }, /** * @param {User} user * @param {Point} point * @returns {vow.Promise} */ checkPoint: function (user, point) { var requestParams = { userId: user.getId(), pointId: point.getId() }; return rest.request('list', 'checkPoint', requestParams, 'POST'); } };
      
      







मॉड्यूल / सूची / दाल / नोड.जेएस
 var _ = require('lodash'); var memcache = require('../../dal/node/memcache'); var Point = require('../../Point'); /** *       * @param {User} user * @returns {String} */ function getListKey(user) { return 'list_' + user.getId(); } module.exports = { /** * @param {User} user * @returns {vow.Promise} resolve with {Point[]} */ getPoints: function (user) { return memcache.get(getListKey(user)) .then(function (points) { if (points) { try { points = _.map(JSON.parse(points), function (point) { return new Point(point); }); } catch (e) { points = []; } } else { points = []; } return points; }); }, /** * @param {User} user * @param {Point} point * @returns {vow.Promise} resolve with {Point} */ addPoint: function (user, point) { return this.getPoints(user) .then(function (points) { point.setId('point_' + (new Date().getTime())); points.push(point); return memcache.set(getListKey(user), JSON.stringify(points)) .then(function () { return point; }); }); }, /** * @param {User} user * @param {Point} point * @returns {vow.Promise} */ checkPoint: function (user, point) { return this.getPoints(user) .then(function (points) { var p = _.find(points, function (p) { return p.getId() === point.getId(); }); if (!p) { throw 'Point not found'; } p.check(); return memcache.set(getListKey(user), JSON.stringify(points)); }); } };
      
      







मॉड्यूल / सूची / दाल / package.js
 { "name" : "dal", "main" : "./node.js", "browser": "./browser.js" }
      
      







 // modules/list/list.js //   var _ = require('lodash'); var vow = require('vow'); var console = require('console'); // DAL- var dal = require('./dal'); //    var Point = require('../Point'); var user = require('../user'); var list = {}; var cache = {}; //   /** *       * @param {Point} newPoint * @returns {vow.Promise} resolve with {Point} */ list.addPoint = function (newPoint) { /* ... */ } /** *     * @param {String} pointId * @returns {vow.Promise} */ list.checkPoint = function (pointId) { /* ... */ } /** *      * @returns {vow.Promise} resolve with {Point[]} */ list.getPoints = function () { console.log('list / getPoints'); return new vow.Promise(function (resolve, reject) { var userId = user.getId(); if (_.isArray(cache[userId])) { resolve(cache[userId]); return; } dal.getPoints(user) .then(function (points) { cache[userId] = points; console.log('list / getPoints: resolve', cache[userId]); resolve(points); }, reject); }); } module.exports = list;
      
      





संरचनात्मक रूप से, हमारे एप्लिकेशन के मॉड्यूल इस तरह दिखने लगे:

 modules ├── dal │  ├── browser │  │  ├── rest.js │  │  └── sessionStorage.js │  └── node │  └── memcache.js ├── list │  ├── dal │  │  ├── browser.js //  dal/browser/rest.js │  │  ├── node.js //  dal/node/memcache.js │  │  └── package.json │  ├── list.js │  └── package.json ├── Point │  ├── package.json │  └── Point.js └── user ├── dal │  ├── browser.js //  dal/browser/sessionStorage.js │  ├── node.js │  └── package.json ├── package.json └── user.js
      
      







सभी एक साथ



यह हमारे आवेदन के तर्क को लागू करने का समय है। टूडू सूची में एक नया आइटम जोड़कर शुरू करते हैं:

 // client/todo.js // ... //    -           var console = require('console'); var _ = require('lodash'); var list = require('../modules/list'); var Point = require('../modules/Point'); var todo = { addPoint: function (description) { var point = new Point({ description: description }); list.addPoint(point); } }; // ...
      
      





क्या होता है जब todo.addPoint ('टेस्ट') कहा जाता है? मैं आरेखों में मुख्य चरणों को चित्रित करने का प्रयास करूंगा। सबसे पहले, ब्राउज़र में मॉड्यूल की बातचीत पर विचार करें:

चार्ट




जैसा कि आप देख सकते हैं, सूची मॉड्यूल 2 बार अपने डीएएल मॉड्यूल तक पहुंचता है, जो हमारे एपीआई के लिए http अनुरोध करता है।

यह सर्वर साइड पर समान (अधिकांश भाग के लिए) मॉड्यूल की परस्पर क्रिया कैसे होती है:

बड़ा चार्ट




यहां ऐसा होता है: डोमेन मॉड्यूल और ब्राउज़र में और सर्वर पर डीएएल मॉड्यूल के बीच बातचीत योजना समान है। DAL स्तर पर संचार प्रोटोकॉल और डेटा स्रोत अलग-अलग हैं, जैसा कि हमने योजना बनाई थी।



आइटम के स्ट्राइकथ्रू के साथ मामला इसी तरह काम करेगा:

 list.checkPoint(pointId);
      
      





बस कुछ मिनट - और हमारा आवेदन तैयार है।

कोड: पठनीय?
 // client/todo.js (function () { var console = require('console'); var _ = require('lodash'); var list = require('../modules/list'); var Point = require('../modules/Point'); var listContainer = document.getElementById('todo_list'); var newPointContainer = document.getElementById('todo_new_point_description'); var tmpl = '<ul>' + '<% _.forEach(points, function(point) { %>' + '<li data-id="<%- point.getId() %>" data-checked="<%- point.getIsChecked() ? 1 : \'\' %>" class="<% if (point.getIsChecked()) { %>todo_point_checked <% }; %>">' + '<%- point.getDescription() %>' + '</li><% }); %>' + '</ul>'; var todo = { addPoint: function (description) { var point = new Point({ description: description }); list.addPoint(point) .then(todo.render, todo.error); }, checkPoint: function (pointId) { list.checkPoint(pointId) .then(todo.render, todo.error); }, render: function () { list.getPoints() .then(function (points) { listContainer.innerHTML = _.template(tmpl, { points: points }); }); }, error: function (err) { alert(err); } }; newPointContainer.addEventListener('keyup', function (ev) { if (ev.keyCode == 13 && ev.ctrlKey && newPointContainer.value) { todo.addPoint(newPointContainer.value); newPointContainer.value = ''; } }); listContainer.addEventListener('click', function (ev) { var targetData = ev.target.dataset; if (!targetData.checked) { console.debug(targetData.checked); todo.checkPoint(targetData.id); } }); todo.render(); })();
      
      







रिपोजिटरी कोड : जीथब



समझ



क्यों, वास्तव में, यह सब बातचीत? फिलहाल, मुझे किए गए काम से कुछ नैतिक संतुष्टि मिली है और इसकी उपयुक्तता के बारे में कई सवाल हैं। यह मॉडल जटिल परियोजनाओं के लिए कितना उपयुक्त है और इसके आवेदन की सीमाएँ कहाँ हैं? क्या मैं अपरिहार्य ओवरहेड के साथ तैयार होने के लिए तैयार हूं जो क्रॉस-प्लेटफॉर्म मॉड्यूल पर आधारित एक एप्लिकेशन होगा?



जब तक पूरी समझ दूर है। किसी भी मामले में, कुछ समान करने और संभावनाओं के बारे में सोचने में सक्षम होना अच्छा है। क्रॉस-प्लेटफॉर्म चौखटे और घटक परीक्षण - क्यों नहीं?




All Articles