【问题标题】:API - Call the same method internally and externallyAPI - 内部和外部调用相同的方法
【发布时间】:2018-08-03 13:25:32
【问题描述】:

我正在使用 Express 制作一个 API,它集成了两个不同的平台,就像一座桥梁。假设我有一个 Products Controller,它具有您期望的默认 CRUD 操作。

让我们创建一个产品。正常的方法是只创建一个路由,自动注入req和res,像这样:

app.post('/api/products', productsController.create)

在我的控制器中,我会创建一个这样的函数:

const create = (req, res) =>
    ...

在外部调用这个方法会起作用,因为在函数内部我会从请求中提取参数然后创建产品。

但是,我有另一条路线和另一个功能将产品从一个地方同步到另一个地方,所以我需要在内部使用 productsController.create,但我不能使用它,因为它需要 req 和 res 来创建产品。

处理这个问题的最佳方法是什么?

我可以将我的控制器函数更改为只接受处理过的字段,然后在路由内部,我可以处理参数并调用函数。唯一的问题是路线会变得更大更丑,而现在每条路线只有一条线。

或者我可以创建另一个模块来处理字段,然后调用 create 函数,如下所示。它在路线内部有点小,但仍然比只有一条线更难看。

const create = product =>
    ...

app.post('/api/products', (req, res) => {
    let product = treatFields(req.params)
    productsController.create(product)
    // send the response
})

无论如何,有人知道这样做的好方法吗?

【问题讨论】:

  • 第二种方法比第一种好。您应该将控制器和模型解耦。这将帮助您重用您的代码。

标签: javascript node.js api express controller


【解决方案1】:

在重要的 Node.js API 中,除了控制器之外,您通常还有一个服务层。所以,控制器只提取参数,有时可能会验证它们。您的服务层负责业务逻辑。

类似这样的结构:

server/
    controllers/
        products-controller          - the REST router[1]
        something-else-controller    - another router[1]
    services/
        products-service             - the "business logic" [2]

现在,您的路由器(上面标记为 [1])接受参数。例如。要获取产品,他们会使用产品 ID 或产品名称:

const router = require('express').Router();
const productsService = require('../services/products-service');
router.get('/products', (req, res, next) => {
    const count = req.query.count ;
    // maybe validate or something
    if (!count) {
        return next(new Error('count param mandatory'));
    }
    productsService.getAllProducts(count)
        .then(products => res.json(products))
        .catch(err => next(err));
});
router.get('/products/:id', (req, res, next) => {
    const id = req.params.id;
    if (id.length !== whatever ) {
        return next(new Error('Id not lookin good'));
    }
    productsService.getProductById(id)
        .then(product => res.json(product))
        .catch(err => next(err));
});

// and "another" router file, maybe products in a category
router.get('/categories/:catId/products', (req, res, next) => {
    const catId = req.params.catId;
    productsService.getProductByCategory(catId)
        .then(products => res.json(products))
        .catch(err => next(err));
});

您的服务层会执行所有数据库逻辑,可能还有“业务”验证(例如,确保电子邮件有效或产品在更新时具有有效价格等):

const productService = {
    getAllProducts(count) {
        return database.find(/*whatever*/)
            .then(rawData => formatYourData(rawData)); // maybe strip private stuff, convert times to user's profile, whatever
            // important thing is that this is a promise to be used as above
    },
    getProductById(id) {
      if (!id) {
        // foo
        return Promise.reject(new Error('Whatever.'));
      }
      return database.findSomethingById(id)
          .then(rawData => formatData(rawData)); // more of the same

    },
    getProductByCategory() {
      return []:
    }
}

现在,我混合了双方的参数验证。如果你想让你的 REST(“web”)层更干净,只需传递参数而不检查,例如productService.getProducts(req.query.page, req.query.limit).then(result => res.json(result);。并在您的服务中进行更多检查。

我什至经常将我的服务分解为多个文件,如下所示:

services/
    product-service/
        index.js // "barrel" export file for the whole service
        get-product.js
        get-products.js
        create-product.js
        delete-product.js
        update-product.js
        product.-utilsjs  // common helpers, like the formatter thingy or mabye some db labels, constants and such, used in all of the service files.

这种方法使整个事情变得更加可测试和可读。虽然文件更多,但与您通常的 node_modules unholly mess 相比,这不算什么 :)

【讨论】:

  • 非常感谢,这看起来很干净。我会等待更多的答案,但我想我会选择这个。是否通常将其称为“服务”或有替代品?
  • 嗯,这是比较常见的,但有不同的方法来解决这个问题。 “服务”可能是从 Java 世界转移过来的(他们做后端的时间比我们使用 JavaScript 和 Node 的时间长得多)。
【解决方案2】:

首先,控制器不应该处理任何业务逻辑,它们旨在为用户提供与您的服务交互的接口,仅此而已,它们应该只验证接口本身(即设置了查询或路径参数正确并在出现错误时确定 HTTP 响应代码)。相反,我将处理返回承诺的逻辑,以执行以下操作:

function createProduct(Product){
    return new Promise((resolve, reject)=>{
        // run your validations and if there is an error
        // if no error
        resolve(product.id);
        // else
        reject(MY_ERROR_REASON);
    });
}

// version 1
function createRoute(req, res){
    const product = treatFields(req.params)
    createProduct(product).then((id)=>{
        res.send(id);
    }).catch((e)=>{
        res.status(400).send(e);
    });
}

//version 2 (for node >7)
async function createRoute(req, res){
    const product = treatFields(req.params)
    try{
        const id = await createProduct(product);
        res.send(id);
    }
    catch(e){
        res.status(400).send(e);
    }
}

app.post('/api/products', productsController.createRoute);

【讨论】:

    【解决方案3】:

    这个问题更多的是关于架构。一种可能的选择是您可以遵循分层架构并创建以下层。

    • 路线
      app.post('/api/products', productsController.create) ....
    • 控制器
      将负责从请求中提取参数,验证并要求服务层执行特定业务,然后从服务层返回日期(通过使用视图/映射器)
    • 服务/门面
      将负责执行特定业务。例如,在您的情况下,bridge 功能 将从此处与其他平台进行通信。如果需要,服务层将与模型进行通信以进行数据库操作。
    • 模型
      将负责数据库操作。

    现在您可以从任何控制器/测试中调用服务的功能。您可以随心所欲地推进应用程序的架构。

    注意:您还可以合并我们主要在 Node 项目中执行的路由和控制器。

    【讨论】:

    • 就像@Zlatko,良好的架构。完美解决我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-31
    • 2011-07-22
    • 2019-04-16
    • 1970-01-01
    • 1970-01-01
    • 2021-04-30
    相关资源
    最近更新 更多