有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

javascript如何在NodeJs中应用Java/Spring的分层架构?

我一直在努力学习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。如何构造具有多个端点和DB事务的大型项目

问候


编辑-

考虑下面的场景-

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

在爪哇-

@Service
public class UserService {

    @Autowired
    UserDetailRepository userDetailRepository;

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

控制器。爪哇-

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

        return UserService.getUserDetail(true);
}

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

控制器。爪哇

@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的用户列表,但这种方法的可扩展性不是很强。必须将业务逻辑与控制器完全分离


共 (3) 个答案

  1. # 1 楼答案

    用户服务。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();
    

    应用程序。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,我更喜欢使用函数方式而不是类

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

    我在这里要说的是,设计模式大多与语言无关。如果你熟悉lang,你总能找到一种处理好分离关系的方法

    现在我有意在上面的示例中使用类,但作为用户服务的函数并不会降低它的可重用性。我不明白你为什么认为“它不可伸缩”

  2. # 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

    让我知道你的想法

  3. # 3 楼答案

    不是一个真正的答案。。。但我有一些想法,因为我来自C#,三年前开始从事NodeJs开发

    依赖注入

    遗憾的是,在我看到的许多NodeJs项目中,这很少使用。有一些npm模块提供了这一功能,但没有一个模块能让我信服正确的方法和API。依赖注入仍然是完全可能的,只是因为需要编写一些样板代码,所以有点难看。不幸的是,没有简单的方法。所以,是的,你可以像Spring一样进行依赖注入,但不是那么方便。但我的观点并不能阻止你们研究node的依赖注入库

    架构

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

    请注意,您可能会看到更多的“函数式编程”模式出现在NodeJs社区中,这并不坏(但OOP也不坏)

    How to structure large projects having multiple endpoints and DB transactions.

    忽略你在那里看到的不好的例子,只需跟随你的直觉,从你的Java体验中了解如何用正确的层、责任和设计模式来构造你的代码。只需记住,你没有接口,没有简单的替代方案。所以你必须学会如何解决这个问题,但是你仍然可以在没有它们的情况下编写优雅的代码

    中间件

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

    存储库

    许多人直接使用MongoDb/Mongoose/SQL客户机,而不使用存储库之类的适配器模式,从而将MongoDb查询泄漏到他们的解决方案中

    我给你的建议是,使用你熟悉的结构和方法,但使用JavaScript提供的工具。我真的很喜欢JavaScript,但当我看到主流资源中缺乏设计和架构时,我会感到畏缩,当然小项目和PoC不需要广泛的设计,但通常当项目增长时,它们不会清理干净。你如何组织一个项目应该与你所使用的语言和框架无关

    快乐编码😄