Как применить многоуровневую архитектуру Java / Spring в NodeJs? - PullRequest
0 голосов
/ 09 апреля 2019

Я уже довольно давно пытаюсь изучить 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 выполняет вызов БД, а также возвращает представление. Таким образом, бизнес-логика и операция CRUD выполняются в одном месте.

Я родом из мира JAVA и там мы делаем это немного по-другому. Например, приложение Spring Boot будет содержать следующую структуру -

  • DTO
  • Репозиторий
  • Услуги
  • Контроллер

Итак, классы controller являются фактическими конечными точками, которые не выполняют никакой бизнес-логики, но вызывают базовый класс service для обработки всего этого. Классы service реализуют бизнес-логику и сохраняют / извлекают данные, необходимые для нее, с помощью классов repository. Хранилище, с другой стороны, обрабатывает операции CRUD.

Это казалось нормальным способом разработки программного обеспечения. Учитывая, что каждый класс имеет свои определенные роли, становится действительно легко обрабатывать любые изменения.

Я понимаю, что NodeJs является динамическим, но -

1. Есть ли способ выделить функциональность, как мы делаем в Spring? Если нет,

2. Как структурировать большие проекты с несколькими конечными точками и транзакциями БД.

Привет


РЕДАКТИРОВАТЬ -

Рассмотрим следующий сценарий -

У меня есть требование, в котором мне нужно получить список пользователей из базы данных, состояние которой равно True (предположим, что состояние - это логическое поле в модели).

На Яве -

@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);
}

Теперь, если требование изменяется, и требуется новая конечная точка, которая возвращает только первые 10 пользовательских данных, статус которых равен true. В этом случае мы можем добавить новый контроллер и ограничить количество возвращаемых результатов до 10. Мы можем использовать один и тот же бизнес-логический класс / класс обслуживания.

*

Controller.java 1064 *

@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 голос
/ 09 апреля 2019

Идея:

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 /userDetails или GET /userDetails?limit=10.

Дайте мне знать, что вы думаете.

1 голос
/ 10 апреля 2019

Не совсем ответ ... но некоторые мысли, поскольку я пришел из C # и начинал как разработчик NodeJs 3 года назад.

Внедрение зависимостей

Это(к сожалению) редко используется во многих проектах NodeJ, которые я вижу.Есть несколько модулей npm, которые предоставляют эту функцию, но ни один из них не убедил меня в правильном подходе и API.Внедрение зависимостей все еще возможно, только немного уродливее из-за некоторого стандартного кода, который вы должны написать.Так что, к сожалению, нелегко @autowire подходит.Так что да, вы можете сделать Dependency Injection как в Spring, но не так удобно.Но позвольте моему мнению не помешать вам исследовать библиотеки внедрения зависимостей для узла.

Архитектура

Вы все еще можете использовать концепции DTO, репозиториев, сервисов и контроллеров.Только по какой-то (нечетной) причине большинство учебных пособий и руководств там забывают об архитектуре здравого смысла и просто бросают все в одном контроллере.Не позволяйте им соблазнить вас, забыв о концепциях, которые вы изучили в Java.Не то, чтобы у ООП и Java не было недостатков, но есть разница в «написании кода стиля JavaScript» и «давайте забудем о правильной архитектуре все вместе».

Обратите внимание, что вы можете увидеть еще несколько «функциональных»в сообществе NodeJs появляются шаблоны программирования, что неплохо (но и не является ООП).

Как структурировать большие проекты с несколькими конечными точками и транзакциями БД.

Игнорируйте плохие примеры, которые вы видите там, просто следуйте своим интуитивным ощущениям из опыта Java, как структурировать свой код с правильными уровнями, обязанностями и шаблонами проектирования.Только помните, что у вас нет интерфейсов, и нет легкой альтернативы этому.Поэтому вам придется научиться обходить это, но вы все равно можете писать элегантный код без них.

Middleware

Middleware - это общая концепция для NodeJприложения, они похожи на прокси / аспектно-ориентированный стиль программирования.Вы можете найти примеры проектов с чрезмерным количеством промежуточного программного обеспечения, не позволяйте себе этим также поддаться искушению.Промежуточное программное обеспечение хорошо для аутентификации / сериализации / заголовков / безопасности, но не для бизнес-логики.Я видел разработку промежуточного ада и промежуточного программного обеспечения, это не красиво.

Репозиторий

Многие люди напрямую используют клиент MongoDb / Mongoose / SQL безиспользуя шаблон адаптера, такой как Repository, утечка запросов MongoDb по всему их решению.

Мой совет для вас будет, просто используйте ту же структуру и подход, с которым вы знакомы, но с инструментами, которые дает вам JavaScript.Я искренне люблю JavaScript, но это заставляет меня вздрогнуть, когда я смотрю на отсутствие дизайна и архитектуры, которые я вижу в основных ресурсах, уверен, что небольшие проекты и PoC не нуждаются в обширном дизайне, но часто, когда проекты растут, они не делаютубиратьТо, как вы структурируете проект, не должно зависеть от языка и структуры, на которой вы пишете.

Счастливое кодирование ?

1 голос
/ 09 апреля 2019

UserService.js

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

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

export const userService = new UserService();

UserController.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 метода контроллера различий.

Я хочу подчеркнуть, что шаблоны проектирования в основном не зависят от языка.Если вы знакомы с языком, вы всегда можете найти способ хорошо справиться с разделением интересов.

Теперь я намеренно использую класс в вышеприведенном примере, но функция, выполняющая роль UserService, не делает его меньшемногоразовые.Я не понимаю, почему вы думаете, «это не масштабируется».

...