【问题标题】:How to apply layered architecture of Java/Spring in NodeJs?如何在 NodeJs 中应用 Java/Spring 的分层架构?
【发布时间】:2019-08-31 00:20:27
【问题描述】:

我已经尝试学习 NodeJS 有一段时间了。所有的书籍和教程似乎都遵循类似的代码结构模式。示例 -

const express = require('express');

const app = express();
app.set('view engine','hbs');

app.get('/', (req, res) =>{
    res.render('index');
});

app.get('/getName', (req, res) =>{
    // Mock DB call to fetch Name
    res.render('displayName');
});


app.listen(3000, () => {
    console.log("Started server on port : 3000");
});

正如您在上面看到的,/getName 控制器正在执行 DB 调用以及返回视图。所以业务逻辑和 CRUD 操作是在同一个地方完成的。

我来自JAVA 的世界,我们的做法略有不同。例如,Spring Boot 应用程序将包含以下结构 -

  • DTO
  • 存储库
  • 服务
  • 控制器

因此,controller 类是不执行任何业务逻辑但调用底层service 类来处理所有这些的实际端点。 service 类在 repository 类的帮助下实现业务逻辑和持久化/获取所需的数据。另一方面,存储库处理 CRUD 操作。

这似乎是一种理智的软件开发方式。鉴于每个类都有其定义的角色,因此处理任何更改都变得非常容易。

我知道NodeJs 是动态的,但是-

1.有没有办法像我们在 Spring 中那样分离功能?如果没有,

2。如何构建具有多个端点和数据库事务的大型项目。

问候


编辑 -

考虑以下场景 -

我有一个需求,我需要从数据库中获取状态为 True 的用户列表(假设状态是模型中的布尔字段)。

在 Java 中 -

@Service
public class UserService {

    @Autowired
    UserDetailRepository userDetailRepository;

    @Override
    public UserDetail getUserDetail (boolean status) {
        UserDetail userDetail  = UserDetailRepository .findUserDetailByStatus(status);
        return userDetail  ;
    }

Controller.java -

@GetMapping("/getUserDetails")
        public ArrayList<UserDetail> getUserDetail(){

        return UserService.getUserDetail(true);
}

现在,如果需求发生变化并且需要一个新的端点,它只返回状态为 true 的前 10 个用户详细信息。在这种情况下,我们可以添加一个新的控制器,并将返回的结果限制为 10。我们可以使用相同的业务逻辑/服务类。

Controller.java

@GetMapping("/getUserDetailsTop10")
            public ArrayList<UserDetail> getUserDetail(){

            List<UserDetails> users = UserService.getUserDetail(true);
            // Filter the list to send top 10
            // return users .
    }

如果我必须在 NodeJS 中实现相同的用例,我将不得不编写业务逻辑来两次获取用户 -

const express = require('express');

const app = express();
app.set('view engine','hbs');

app.get('/getUserDetails', (req, res) =>{
    // Call DB and get users whose status is True
    res.send(userdetails);
});

app.get('/getUserDetailsTop10', (req, res) =>{
    // Call DB and get users whose status is True
    // Filter the returned results and limit the result to 10 values only
    res.send(userdetails);
});


app.listen(3000, () => {
    console.log("Started server on port : 3000");
});

充其量,我可以将此逻辑抽象为一个函数,该函数将返回一个状态为 True 的用户列表,但这种方法的可扩展性也不是很好。业务逻辑必须与控制器完全分离。

【问题讨论】:

  • 问题的格式不是问题,问题的内容才是。有数百种不同的可能同样有效的答案。 Node.js 生态系统丰富多样,没有一种方法可以做到这一点。请务必查看上面的链接,尤其是this one。并不是说这不是一个完全合理的问题,只是不适合 this 网站的格式。
  • 类似问题在这里有一定程度的讨论stackoverflow.com/questions/5178334/…
  • 嗯,我不是回答你问题的最佳人选。在后端 js 方面没有那么有经验,但我会尝试。 Js 社区拥抱轻量级的东西。我们得到了对一流函数、事件循环等具有原生支持的运行时。这样,我们倾向于自然地在管道链、可观察流、发布/订阅等模式中寻求解决方案。我们也有关注点分离,但不像 java 风格。
  • "唯一的方法是创建一个可以被多个端点引用的函数。" - 是的,这正是你要做的。 (当需要时,否则 YAGNI)。你可以把这些函数放在它们自己的模块、文件中,如果你愿意的话,甚至可以把它们写成类。我不明白你为什么认为这不够充分或可扩展,或者你为什么不认为它来分离关注点。
  • 我认为你在 js 领域面对多范式善良时感到概念上的不适。 Java 在函数式编程方面确实有一些负担,但在 js 中没有。 FP 或 OOP,我会根据用例选择合适的。

标签: javascript java node.js spring design-patterns


【解决方案1】:

并不是一个真正的答案...但是一些想法,因为我来自 C#,并在 3 年前开始成为一名 NodeJs 开发人员。

依赖注入

这在我看到的许多 NodeJs 项目中(很遗憾)很少使用。有一些 npm 模块提供了这个功能,但没有一个模块能用正确的方法和 API 说服我。依赖注入仍然是完全可能的,只是因为你必须编写一些样板代码而有点丑陋。可悲的是,不容易接近@autowire。所以是的,您可以像使用 Spring 一样进行依赖注入,但不那么方便。但我的意见不会阻止你研究 node 的依赖注入库。

架构

您仍然可以使用 DTO、存储库、服务和控制器的概念。仅出于某些(奇怪的)原因,大多数教程和指南都忘记了常识架构,只是将所有内容都放在一个控制器中。不要让它们引诱您忘记您在 Java 中学到的概念。并不是说 OOP 和 Java 没有缺陷,而是“编写 JavaScript 风格的代码”和“让我们一起忘记正确的架构”是有区别的。

请注意,您可能会在 NodeJs 社区中看到更多“函数式编程”模式兴起,这还不错(但 OOP 也不是)。

如何构建具有多个端点和数据库的大型项目 交易。

忽略您在外面看到的糟糕示例,只需按照您从 Java 体验中的直觉来构建代码,使用正确的层、职责和设计模式。请记住,您没有接口,也没有简单的替代方法。所以你必须学会​​用自己的方式解决这个问题,但没有它们你仍然可以编写优雅的代码。

中间件

中间件是 NodeJs 应用程序的一个常见概念,它们类似于面向代理/方面的编程风格。您可能会发现示例项目包含过多的中间件,也不要让自己受此诱惑。中间件适用于身份验证/序列化/标头/安全性,但不适用于业务逻辑。我见过中间件地狱和中间件驱动的开发,它并不漂亮。

存储库

很多人直接使用 MongoDb/Mongoose/SQL 客户端,而不使用像 Repository 这样的适配器模式,在他们的解决方案中泄漏 MongoDb 查询。

我对您的建议是,使用您熟悉的相同结构和方法,但使用 JavaScript 为您提供的工具。我真的很喜欢 JavaScript,但是当我看到主流资源中缺乏设计和架构时,它让我畏缩,当然小项目和 PoC 不需要广泛的设计,但通常当项目增长时,它们不需要清理。您如何构建项目应该与您正在编写的语言和框架无关。

快乐编码?

【讨论】:

  • 嗨@Ian Segers,完全同意你的看法。我可以继续在一个单独的模块中抽象业务逻辑,然后从控制器调用,这样就可以了。但我很想看到 NodeJs 教程实际上执行了这一点。但是,我不确定如何将数据库查询从单独的模块(服务功能)中分离出来。我希望看到一个遵循这些编码标准的结构良好的 NodeJS 项目。
【解决方案2】:

一个想法:

const express = require('express');

const app = express();
const { userDetails } = require('./usersBusinessLogic.js')
app.set('view engine','hbs');

app.get('/getUserDetails', async (req, res) =>{
  const limit = parseInt(req.query.limit || '0')
  if (IsNaN(limit)) {
    res.status(400)
    return res.end('limit expected to be a number')
  }
  const detailsResponse = await userDetails({ limit })
  res.send();
});

然后你把你的逻辑写成function userDetails({ limit, ...rest } = { limit: 0 })

在另一个文件中,即usersBusinessLogic.js:

async function userDetails({ limit, ...restOfTheOptions } = { limit: 0 }) {
  console.log({ limit, restOfTheOptions }) // TODO logic here.
  // const result = await db.find('users', {...})
  // return serialize(result)
}

// function serialize(result) {
//  return JSON.stringify(result)
// }

module.exports = {
  userDetails
}

之后您可以致电GET /userDetailsGET /userDetails?limit=10

让我知道你的想法。

【讨论】:

  • 嗨。这种方法会奏效。至少我们已经将 BL 从控制器中分离出来了。但是你会建议进一步拆分服务模块吗?因此,在我看来,将与 DB 相关的查询保存在单独的模块/类中总是更好。例如,getUsers、saveUsers、getUserByID 等都是 DB(ORM 或其他)查​​询。那么,如果我将其分开,那么引入另一个模块来抽象这些查询是否有意义?因为我不确定使用模块是否是正确的方法,主要是因为我是 JS 新手。但我很想知道你的想法。
  • 它与您想要实现的模块没有特别的联系。这与您的架构决策有关。在我的示例中,我已将 await db.find('users', {...}) 放在评论中,但没有说明它的来源。当然,在模块的顶部,您必须require('./db'),然后在 db.js 模块中,针对数据库实现您的逻辑。是实现 ORM 还是直接使用驱动程序,这取决于您。但是你必须为 BL 暴露一些东西。希望它得到了清除。随时问更多。
【解决方案3】:

用户服务.js

import { userDetailRepository } from '../models/user';

class UserService {
    getUserDetail (status) {
        const userDetail  = UserDetailRepository.findUserDetailByStatus(status);
        return userDetail;
    }
}

export const userService = new UserService();

用户控制器.js

import { userService } from '../services/user';

@RouterAdapter
class UserController {
  @GetMapping("/getUserDetails")
  getUserDetail() {
    return userService.getUserDetail(true);
  }
  @GetMapping("/getUserDetailsTop10")
  getUserDetailsTop10() {
    return userService.getUserDetail(true).slice(10);
  }
}

export const userController = new UserController();

app.js

import * as express from 'express';
import { userController } from './controllers/user';

const app = express();
app.set('view engine','hbs');

app.use(userController);

app.listen(3000, () => {
    console.log("Started server on port : 3000");
});

我故意“变形”我的 js 代码以匹配 java 风格,所以也许你有宾至如归的感觉。我个人不会这样写js,比起class,我更喜欢用函数的方式。

我没有包括我使用的两个装饰器 (@RouterAdapter, @GetMapping) 的实现,这是一个与讨论无关的细节。我可以向你保证,这在 js 中是可行的。唯一缺少的是js没有运行时方法重载,所以我要命名2个diff控制器方法。

我在这里要强调的一点是,设计模式大多与语言无关。如果您熟悉该语言,您总能找到一种方法来很好地处理关注点分离。

现在我在上面的例子中有意使用了类,但是作为 UserService 的函数并没有降低它的可重用性。我不明白您为什么认为“它无法扩展”。

【讨论】:

  • 这肯定行得通。但是,是的,我同意你的观点,在 Node 中使用类来构造代码似乎是 Java 的丑陋版本:p 我想知道,这真的是 Node 中大型项目的处理方式吗?或者我假设分层架构的重要性是错误的。此外,在我之前的评论中,“它无法扩展”是指,为了将逻辑与控制器分离,我需要将所有逻辑写入服务类/函数中。但是对于所有存在业务逻辑的情况,都需要进行这种抽象。我们不能随心所欲地挑选函数
  • 另外,您对 DB 相关操作有何看法?就像我必须从 DB 中持久化/获取某些东西一样,我将调用 sql 客户端连接器并在 NodeJs 中执行查询。这段逻辑可以直接进入服务类。有没有办法抽象这个?因此,我假设要模拟存储库行为,我们可以创建另一个模块,该模块基本上具有以函数形式为特定实体定义的所有 CRUD 操作。然后在服务层调用这些函数。那么 - Controller -> Service -> Repo 会有明确的分离?
  • 最后,为什么 NodeJs 教程/资源不讨论这个?是因为 Node 不用于会遇到此类问题的大型项目,还是有替代策略来处理此类问题?
  • @BoudhayanDev 1. 希望在问题下的 cmets 中回答,不明白为什么cherry-pick fn 不可接受,也许你只是想一次通过一堆相关函数,在这种情况下您可以将它们包装在一个普通的对象中。
  • @BoudhayanDev 2。我个人同意您的“假设”,但我还是个前端人员,而不是最好的回答者。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-26
  • 2011-05-06
  • 2014-06-20
  • 2015-03-15
  • 2011-03-26
  • 2012-09-04
相关资源
最近更新 更多