65.9K
CodeProject 正在变化。 阅读更多。
Home

Nodejs Web API 的 WCF 设计模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (3投票s)

2016 年 9 月 13 日

CPOL

3分钟阅读

viewsIcon

11180

downloadIcon

98

将 WCF 设计模式应用于 Nodejs Web API

引言

当你开始使用 nodejs 时,首先会遇到的问题是 Javascript 中编写代码的动态方式。 几乎不可能找到一个标准或模式来编写体面的 nodejs Web API 后端。 本文试图提出一种关于将 WCF 设计模式引入 nodejs 的理论,在阅读本文之前,您可以充分利用 https://documenter.getpostman.com/view/258093/example/2Jwb6h 

背景

WCF 是一种很棒的设计模式; 如果我们忽略配置上的麻烦,它会保持一切就绪。 您有数据契约,它允许向消费者公开所需的数据,操作契约用于呈现服务方法,终结点是一种很好的方式来告诉如何使用服务。那么我们如何在 nodejs 中实现 WCF 设计模式。

使用代码

为了使 WCF 设计模式与 nodejs 一起工作,一切都将从文件结构开始。这是我的建议,请注意以下结构是高级别的

-App
--dataContracts
--IApp
--logs
--operationsContract
--web.config
--package.json

在下一节中,我将尝试解释每个文件夹的作用以及我们需要它的原因。   

结构解释

App

应用程序的根文件夹,它包含应用程序所需的一切,例如 node_models、YAML 文件或应用程序需要的任何额外信息,都将其设置在根文件夹中,请注意 App 文件夹的名称可以是您的应用程序名称。

dataContracts

在此文件夹中,您声明需要向使用者公开的所有模型。 每个模型都将有一个文件夹;每个文件夹将有一个 index.js 文件,用于导出模型,例如,此 index.js 文件正在公开一个 mongoose 模式。

var mongoose = require('mongoose');

var schema = new mongoose.Schema({
   name: String, 
   isActive: {
       type: Boolean,
       default: true
   }, 
   isDeleted: {
       type: Boolean, 
       default: false
   }, 
   createDate: {
       type: Date, 
       default: new Date
   }, 
   latitude: String, 
   longitude: String
});

module.exports = mongoose.model('categories', schema);

稍后我们将看到如何在运行应用程序之前在 mongoose 驱动程序中注册此类型,现在此文件夹的文件结构如下所示:

-IApp
--dataContracts
---applications
----index.js
---categories
----index.js
---cities
----index.js
---friendships
----index.js
---otp
----index.js
---products
----index.js
---users
----index.js
--logs
--operationsContract
--web.config
--package.json

operations contract

现在,在声明我们的数据契约之后,是时候编写操作契约了。 这部分起初可能看起来很陌生,但我的工作方式只是分离事物,因此对于每个方法,我都创建一个单独的文件,其中包含此方法的名称,然后 index 文件将导出每个方法。 例如,如果我为模型类别创建了方法 create,那么我将在 operations Contract 文件夹中创建一个名为 categories 的新文件夹。 现在在该文件夹中,我将创建一个名为 create.js 的文件,并在其中编写我的函数并将其导出为 execute,以下是代码:

var mongoose = require('mongoose');
var categoryCollection = mongoose.model('categories');
var responseDictionary = require('response-dictionary');
var verifyingCategory = require('./verifying');

exports.execute = function(httpRequest, httpResponse) {
    try {
        verifyingCategory.isValidForCreate(httpRequest.body, function(categoryCreationError) {
            if (!categoryCreationError) {
                var category = new categoryCollection(httpRequest.body);
                category.save(function(savingcategoryError) {
                    if (!savingcategoryError) {
                        return responseDictionary.getAsHttpResponse(1008, category, null, httpResponse);
                    }
                    else {
                        return responseDictionary.getAsHttpResponse(1500, null, savingcategoryError, httpResponse);
                    }
                });
            }
            else {
                return httpResponse.status(categoryCreationError.httpStatus).send(categoryCreationError);
            }
        });
    }
    catch (exception) {
        return responseDictionary.getAsHttpResponse(1500, null, exception, httpResponse);
    }
};

您的函数可以是您想要的任何函数,但请注意,我传递了 HTTP 请求,以便我可以完全控制来自使用者的请求。 现在要公开此函数,我们使用 operations contract 中每个文件夹中的 index.js。 这是完整的 index.js 文件:

var category = function() {};

category.prototype.create = function execute(httpRequest, httpResponse) {
  require('./create').execute(httpRequest, httpResponse);
};

category.prototype.update = function execute(httpRequest, httpResponse) {
  require('./update').execute(httpRequest, httpResponse);
};

category.prototype.delete = function execute(httpRequest, httpResponse) {
  require('./delete').execute(httpRequest, httpResponse);
};

category.prototype.get = function execute(httpRequest, httpResponse) {
  require('./get').execute(httpRequest, httpResponse);
};

category.prototype.getById = function execute(httpRequest, httpResponse) {
  require('./getById').execute(httpRequest, httpResponse);
};


module.exports = category;

让我们看看到目前为止的文件结构是什么样子的:

-IApp
--dataContracts
---applications
----index.js
---categories
----index.js
---cities
----index.js
---friendships
----index.js
---otp
----index.js
---products
----index.js
---users
----index.js
--logs
--operationsContract
---applications
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---categories
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---cities
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---friendships
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---otp
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---products
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---users
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
--web.config
--package.json

Web.Config

现在操作和数据契约已设置就绪,是时候将它们连接在一起了。 首先,让我们看一下 web.config 文件夹。 它有一个名为 index.js 的主文件; 让我们看一下:

module.exports = {
    appName: 'families',
    contracts: ['applications', 'cities', 'categories', 'users', 'otp', 'friendships', 'products'],
    responses: responses.responses,
    isHttpsEnabled: false,
    enableLogging: true,
    port: 8901,
    connectionString: "mongodb:///families",
    dataBaseName: "families",
    certificates: {
        key: __dirname + '/server/certificates/server.key',
        certificate: __dirname + '/server/certificates/server.crt'
    },
    publicDirectories: [{
        title: '/',
        path: __dirname + '/public'
    }],
    appSetting: {
        uploadingFolder: __dirname + '/public/files'
    }
};
此处的主要设置是 appSetting 部分之前的所有内容。 在 app setting 部分中,您可以编写应用程序中需要的任何内容。
 
最后一个文件夹将命名为 IApp,它表示 WCF 中的 IInterface。 此文件夹只有一个 index.js 文件,它将数据和操作契约链接在一起,将正文解析模块应用于任何 express 应用程序,最后一件事是加载终结点文件:
 
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var cors = require('cors');
var https = require('https');
var fs = require("fs");
var config = require('../web.config');
var app = express();

exports.startup = function() {
    mongoose.connect(config.connectionString);
    app.use(bodyParser.urlencoded({
        extended: true,
        limit: '50mb'
    }));

    app.use(bodyParser.json({
        limit: '50mb'
    }));

    app.use(cors());

    for (var contract = 0; contract < config.contracts.length; contract++) {
        require('../dataContracts/' + config.contracts[contract]);
    }
    
    require('../web.config/endPoints')(app);
    for (var directory = 0; directory < config.publicDirectories.length; directory++) {
        if (fs.existsSync(config.publicDirectories[directory].path) === false) {
            fs.mkdirSync(config.publicDirectories[directory].path);
        }

        app.use(config.publicDirectories[directory].title, express.static(config.publicDirectories[directory].path));
    }

    if (config.isHttpsEnabled === true) {
        https.createServer({
            key: fs.readFileSync(config.certificates.key),
            cert: fs.readFileSync(config.certificates.certificate)
        }, app).listen(config.port);
    }
    else {
        app.listen(config.port);
    }

    console.log(config.appName + ' running on: ' + config.port);
};

this.startup();
注意上面的代码中的这一行:
require('../web.config/endPoints')(app);
endPoints.js 是一个接受 express 对象的文件,以下是此文件的完整代码:
 
var authenticate = require('../lib/authentication');
var applications = require('../operationsContract/applications');
var categories = require('../operationsContract/categories');
var cities = require('../operationsContract/cities');
module.exports = function(app) {
    app.post('/api/applications/', new authenticate().masterKey, new applications().create);
    app.patch('/api/applications/:id', new authenticate().masterKey, new applications().update);
    app.delete('/api/applications/:id', new authenticate().masterKey, new applications().delete);
    app.get('/api/applications/', new authenticate().masterKey, new applications().get);
    app.get('/api/applications/:id', new authenticate().masterKey, new applications().getById);
    app.post('/api/categories/', new authenticate().masterKey, new categories().create);
    app.patch('/api/categories/:id', new authenticate().masterKey, new categories().update);
    app.delete('/api/categories/:id', new authenticate().masterKey, new categories().delete);
    app.get('/api/categories/', new authenticate().applicationKey, new categories().get);
    app.get('/api/categories/:id', new authenticate().applicationKey, new categories().getById);
    app.post('/api/cities/', new authenticate().masterKey, new cities().create);
    app.patch('/api/cities/:id', new authenticate().masterKey, new cities().update);
    app.delete('/api/cities/:id', new authenticate().masterKey, new cities().delete);
    app.get('/api/cities/', new authenticate().applicationKey, new cities().get);
    app.get('/api/cities/:id', new authenticate().applicationKey, new cities().getById);
};

身份验证

任何不需要公开的函数或模块都将写入名为 lib 的文件夹中,并且由于我将完整的 HTTP 请求对象传递给它,因此您可以使用所需的任何身份验证类型,基本 HTTP、oauth 或只是标头中的密钥,例如,以下是一种验证主密钥的方法:

var responseDictionary = require('response-dictionary');
var config = require('rekuire')('web.config');

exports.execute = function(httpRequest, httpResponse, next) {
  try {
    if (httpRequest.header("X-example-MasterKey")) {
      if (httpRequest.header("X-example-MasterKey") === config.masterKey) {
        next();
      }
      else {
        responseDictionary.getAsHttpResponse(2, null, null, httpResponse);
      }
    }
    else {
      responseDictionary.getAsHttpResponse(1, null, null, httpResponse);
    }
  }
  catch (exception) {
    responseDictionary.getAsHttpResponse(1500, null, exception, httpResponse);
  }
};

最后一件事是使用以下命令运行我们的后端:

forever start IExample/
 
 
© . All rights reserved.