Я думаю, вы все согласны с тем, что когда мы пишем приложение на экспресс она через короткое время становится довольно таки трудно читабельным, сложно тестируемым и сложно расширяемым.
Одна из основных проблем, возникающих при работе с экспрессом это то, что контроллеры содержат бизнес-логику и рабата с базами данных, а также с другими сущностями, не принадлежащими контроллерам.
Вы можете поспорить и сказать, что в express есть генератор (cli) благодаря этому генератору можно создать скелет приложения.
И вы отчасти будете правы, потому что экспресс имеет генератор который генерирует нам приложения.
C помощью генератора получаем скелет приложения но это все еще не решает основных проблем, которые возникают при работе с экспрессом (разбиение кода на слои) нигде нет архитектуры, описанной в каком месте хранить бизнес-логику или где именно работать с базой данных.
После генерации приложения мы получаем трудное для тестирования, расширения и чтения приложение. Кроме того, ваше приложение будет полностью привязано к express, и если в будущем вы захотите поменять фрейворк, вам придется переписать весь проект.
Представьте, если бы у вас сразу появился проект с готовой архитектурой, где все разбито на слои и каждый слой не зависит друг от друга, где контроллеры, бизнес-логика и базы данных разделены, и все разделено на модули.
И именно это я предлагаю вам.
Ваша бизнес-логика не будет зависеть от фреймворка, она не будет зависеть от базы данных или от транспортного уровня (http, ws ...)
вы можете переключиться с REST FULL API в любое время в JSON API или что-то еще, для этого вам нужно будет изменить метод одного класса и все, и никакие другие части вашего приложения не будут изменены.
То же самое и с базой данных. Вы не будете привязаны к конкретной базе данных, вы сможете легко и быстро переключаться по базам данных.
Вы можете изменить фреймворк за короткое время (в зависимости от написанного вами приложения), потому что в моей архитектуре фреймворк играет роль транспортного уровня.
Также у вас будет несколько базовых сервисов из коробки, которые сильно помогут вам при разработке приложения, а также полноценная конфигурация eslint, prettier, pre-commit, typescript.
-
трехслойная архитектура
-
шаблон проектирование inversion of control в реализации dependency injection а также контейнер для зависимостей inversion of control container.
-
Шаблон проектирование repository (для разделение БД от бизнес логики)
-
Express.js и inversify-express-utils
-
TypeScript
Идея состоит в том, чтобы использовать принцип разделения ответственности, используя эту архитектуру, мы получаем три уровня.
-
Маршрутизатор, этот слой будет работать с http, ws... а также с express.
-
Сервис, ваша бизнес-логика живет в сервисе.Сам сервис - это класс, который следует принципам SOLID. На этом уровне не должно существовать какой-либо формы «SQL-запроса». В сервисах в 99% случаев не должно попадать объект req, res. Сервисы ничего не должны знать об транспортном уровне http,ws.
-
Слой данных, в этом слое хранится работа с базой данных.
Если вы когда-либо писали на angular или nest.js, то вы уже знакомы с IOC, DI, IOCC.
Nest.js
Angular
Мы будем использовать inversify, который реализует шаблон проектирования IOC в виде DI, а также дает нам IOCC.
Если по какой-то причине вы до сих пор не знаете IOC, DI, IOCC внизу есть ссылки на видео, в котором объясняется, что такое шаблон проектирования IOC и в виде DI, а также контейнер для зависимостей.
Илья Климов senior front-end engineer из Gitlub.
- https://youtu.be/ETyltCwtQHs?list=PLvTBThJr861xKTf1x6P49MwN6yoN4v69k
- https://youtu.be/RwLWYB9C2Tc?list=PLvTBThJr861xKTf1x6P49MwN6yoN4v69k
- https://youtu.be/0IqU_13tMcc?list=PLvTBThJr861xKTf1x6P49MwN6yoN4v69k
- https://youtu.be/icbryEgQPxk?list=PLvTBThJr861xKTf1x6P49MwN6yoN4v69k
- https://youtu.be/pV-YKZEqsQ4?list=PLvTBThJr861xKTf1x6P49MwN6yoN4v69k
Андрей Мелихов senior backend engineer из Яндекс
Суть данного шаблона проектирование состоит в том, чтобы не работать напрямую с БД, а работать через интерфейсы (более подробно можете почитать в википедии)
inversify-express-utils - это библиотека, которая обертывает Express и позволяет нам использовать Express более удобным способом.
import * as express from 'express';
import {
interfaces,
controller,
httpGet,
httpPost,
httpDelete,
request,
queryParam,
response,
requestParam,
} from 'inversify-express-utils';
import { injectable, inject } from 'inversify';
@controller('/foo')
export class FooController implements interfaces.Controller {
constructor(@inject('FooService') private fooService: FooService) {}
@httpGet('/')
private index(
req: express.Request,
res: express.Response,
next: express.NextFunction,
): string {
return this.fooService.get(req.query.id);
}
@httpGet('/')
private list(
@queryParam('start') start: number,
@queryParam('count') count: number,
): string {
return this.fooService.get(start, count);
}
@httpPost('/')
private async create(
@request() req: express.Request,
@response() res: express.Response,
) {
try {
await this.fooService.create(req.body);
res.sendStatus(201);
} catch (err) {
res.status(400).json({ error: err.message });
}
}
@httpDelete('/:id')
private delete(
@requestParam('id') id: string,
@response() res: express.Response,
): Promise<void> {
return this.fooService
.delete(id)
.then(() => res.sendStatus(204))
.catch((err: Error) => {
res.status(400).json({ error: err.message });
});
}
}
Давайте будем реалистами, писать код на не типизированных языках программирования в 2021 году довольно проблематично, особенно если мы собираемся использовать ООП.
Имя | Описание |
---|---|
node_modules | Содержит установленные модели из npm |
dist | Содержит компилированный код проекта. |
src | Содержит не компилированные исходники проекта |
.editorconfig | Конфигурации и набор расширений для многих редакторов кода и IDE. |
.eslintignore | Настройка параметров для eslint, путей, которые будут исключены из линтинга (т.е. игнорируются и линтер на них не влияет) |
.eslintrc | Конфигурация настроек для ESLint, определяющая стиль кодирования. |
.gitignore | Не отслеживайте изменения в файлах с помощью git |
.prettierrc | Конфигурационный файл prettier |
package.json | Файл, содержащий зависимости npm |
tsconfig.json | Файл конфигурации для TypeScript |
yarn.lock | Yarn нужна точная версия для каждой установленной зависимости. Для этого Yarn использует файл yarn.lock в корневом каталоге вашего проекта. |
Имя | Описание |
---|---|
container | Именно внутри этой папки находится контейнер зависимостей приложение |
middleware | Содержит ППО для express |
services | Содержит сервисы которые не являются частью какого либо модуля и выполняют ровно одно действие |
utils | Содержит утилитарные функции |
enums | Содержит enums |
main.ts | Является основной точкой входа для приложение |
Имя | Описание |
---|---|
interfaces.ts | Файл содержит интерфейсы сущностей которые будут inject-тироваться |
tokens.ts | Содержит токены благодаря которым будет inject-тироваться сущности |
inversify.config.ts | В этом файле настраивается контейнер зависимости приложение |
Любой сервис состоит из трех частей
- name.interfaces.ts - В этом файле находится интерфейсы сервиса
- name.service.ts - В этом файле находится логика сервиса
- name.service.spec.ts - В этом файле находится тесты проверяющие логику сервиса
Имя сервиса | Короткое описание сервиса, посмотрите сам сервис чтобы получить больше информации |
---|---|
config | Предоставляет класс, у этого класса есть поля get, с помощью которого вы можете получить файл конфигурации |
debug | Является оберткой над модулем debug. |
generate-token | Является оберткой над модулем jsonwebtoken |
logger | Является оберткой над модулем winston, благодаря этому логгеру вы можете сохранять ошибки, информацию или что-либо еще в базе данных |
Является оберткой над модулем nodemailer, благодаря данному сервису вы можете отправлять электронные письма сущности | |
mongoose-connection | Благодаря этому сервису вы можете подключиться к mongodb через mongoose |
oauth | Содержит методы для работы с vk, google и github, в дальнейшем вы можете без проблем добавлять другие социальные сети |
response | Именно эта служба будет определять, в каком формате должны поступать данные из служб, ресурсов, а также в каком формате данные должны быть отправлены клиенту по умолчанию "REST FULL API" |
validation | |
Является оберткой над модулем joi, благодаря этой службе вы можете проверять объект, строки, массивы и т. д. с помощью схем.сущности |
Папка app: содержит модули вашего приложение.
Любой модуль состоит из шести частей
- name.interfaces.ts - В этом файле находится интерфейсы модуля
- name.service.ts - В этом файле находится логика модуля
- name.controller.ts - В этом файле находится обработка маршрутов для модуля
- name.controller.spec.ts - В этом файле находится тесты для маршрутов модуля
- папка name-resource содержит ресурсы, в данном папке находится файл index.ts который возвращает как минимум один класс который реализует интерфейс ресурса модуля.
- name.module.ts этот файл экспортирует все выше перечисленные сущности
Давайте разберемся как работает аутентификация, авторизация, идентификация, подтверждение email, восстановление пароля через email, access tokens + refresh token + device id в контексте модуля auth.