Говоря о BFF
В последнее время наши партнеры по серверной части начали внедрять архитектуру микросервисов, разделив многие доменные службы. Как большой интерфейс, мы также должны внести изменения. Обычно списку нужен интерфейс для получения данных, но под микро- сервисная архитектура. В середине должен быть уровень, предназначенный для агрегации интерфейсов n интерфейсов в рамках микросервисной архитектуры, что удобно для интерфейсных вызовов, поэтому мы приняли популярный в настоящее время метод BFF.
Между bff и узлом нет сильной связывающей связи, но для фронтенд-персонала слишком дорого быть знакомым с бэкенд-языками, отличными от ноды.Поэтому мы используем узел как средний уровень в стеке технологий, и мы используем nestjs в качестве http-фреймворка node.
Эффект лучшего друга
BFF (Backends For Frontends) — это серверная часть, обслуживающая внешний интерфейс. После нескольких проектов у меня также есть некоторое представление о нем. Я думаю, что в основном он выполняет следующие функции:
- Агрегация интерфейсов и прозрачная передача: в соответствии с вышеизложенным несколько интерфейсов объединяются для облегчения внешних вызовов.
- Форматирование данных интерфейса: интерфейсная страница отвечает только за отрисовку и взаимодействие с пользовательским интерфейсом и не обрабатывает сложные отношения данных. Читабельность и удобство сопровождения внешнего кода будут улучшены.
- Сокращение затрат на координацию персонала: после того, как внутренние микросервисы и большой внешний интерфейс будут реализованы и усовершенствованы, некоторые более поздние требования должны быть разработаны только внешним персоналом.
Применимая сцена
Хотя BFF более популярен, его нельзя использовать для популярности, он должен соответствовать определенным сценариям иИнфраструктураЕго следует использовать только тогда, когда он идеален, иначе он только увеличит затраты на обслуживание проекта и риски, но пользы очень мало.Я думаю, что применимые сценарии следующие:
- Серверная часть имеет стабильные доменные службы и требует уровня агрегации.
- Требования часто меняются, и часто приходится менять интерфейс: бэкенд имеет стабильный набор доменных сервисов для обслуживания нескольких проектов, и стоимость изменений высока, тогда как слой bff нацелен на один проект, а изменения в слой bff может добиться минимальных изменений стоимости.
- Имеется полная инфраструктура: логи, ссылки, мониторинг серверов, мониторинг производительности и т.д. (обязательно)
Nestjs
В этой статье я представлю Nestjs с точки зрения новичка, который занимается только интерфейсом и сервером.
Nest — это платформа для создания эффективных масштабируемых серверных приложений Node.js.
Как работает серверная часть после того, как клиентская часть инициирует запрос
Сначала делаем GET-запрос
fetch('/api/user')
.then(res => res.json())
.then((res) => {
// do some thing
})
Предполагая, что прокси-сервер nginx настроен (все/api
Запросы в начале все идут на наш bff сервис), бэкенд будет получать наш запрос, поэтому вопрос, через что он получен?
Сначала мы инициализируем проект Nestjs и создаем пользовательский каталог, структура каталогов которого выглядит следующим образом.
├── app.controller.ts # 控制器
├── app.module.ts # 根模块
├── app.service.ts # 服务
├── main.ts # 项目入口,可以选择平台、配置中间件等
└── src 业务模块目录
├── user
├── user.controller.ts
├── user.service.ts
├── user.module.ts
Nestjs находится вController
Уровень получает запросы через маршрутизацию, и его код выглядит следующим образом:
user.controller.ts
import {Controller, Get, Req} from '@nestjs/common';
@Controller('user')
export class UserController {
@Get()
findAll(@Req() request) {
return [];
}
}
Вот некоторые основы Nestjs
Использование Nestjs для выполнения базовой услуги требуетModule
,Controller
,Provider
Три части.
-
Module
, буквально означает модуль, вnesjs by@Module()
Декорированный класс — это модуль, и в конкретном проекте мы будем использовать его какВвод текущего подмодуля, Например, полный проект может иметь пользовательские модули, модули управления товарами, модули управления персоналом и так далее. -
Controller
, буквально означает контроллер, ответственный за обработку входящих запросов от клиента и ответов, возвращаемых сервером, официальное определение — это@Controller()
Декорированный класс, приведенный выше код является контроллером, когда мы инициируем адрес как'/api/user'
Когда запрос на получение будет сделан, контроллер найдетfindAll
метод, возвращаемое значение этого метода — это данные, полученные внешним интерфейсом. -
Provider
, буквально означает провайдер, на самом деле он предоставляет услуги для контроллера.Официальное определение:@Injectable()
Для декорированного класса позвольте мне кратко объяснить: приведенный выше код непосредственно выполняет обработку бизнес-логики на уровне контроллера, с последующей бизнес-итерацией требования будут становиться все более и более сложными, и такой код будет сложно поддерживать.Таким образом, слой необходим для обработки бизнес-логики., Провайдер это слой, ему нужно@Injectable()
ретушь.
Давайте улучшим приведенный выше код и добавимProvider
, созданный под текущим модулемuser.service.ts
user.service.ts
import {Injectable} from '@nestjs/common';
@Injectable()
export class UserService {
async findAll(req) {
return [];
}
}
Затем нашему контроллеру нужно внести некоторые изменения.
user.controller.ts
import {Controller, Get, Req} from '@nestjs/common';
import {UserService} from './user.service';
@Controller('user')
export class UserController {
constructor(
private readonly userService: UserService
) {}
@Get()
findAll(@Req() request) {
return this.userService.findAll(request);
}
}
Таким образом, наши контроллер и провайдер завершены, два уровня выполняют свои обязанности, а ремонтопригодность кода повышается.
Далее нам также нужно внедрить Controller и Provider в Module, мы создаем новыйuser.module.ts
файл, напишите следующее:
user.module.ts
import {Module} from '@nestjs/common';
import {UserController} from './user.controller';
import {UserService} from './user.service';
@Module({
controllers: [UserController],
providers: [UserService]
})
export class UsersModule {}
Таким образом, один из наших бизнес-модулей завершен, а остальные нужно толькоuser.module.ts
Внесите его в общий модуль проекта и заинжектите.После запуска проекта вы можете получить данные, обратившись к '/api/user'.Код выглядит следующим образом:
app.module.ts
import {Module} from '@nestjs/common';
import {APP_FILTER} from '@nestjs/core';
import {AppController} from './app.controller';
import {AppService} from './app.service';
import {UsersModule} from './users/users.module';
@Module({
// 引入业务模块
imports: [UsersModule],
controllers: [AppController],
providers: [
AppService
]
})
export class AppModule {}
Общие модули Nestjs
Прочитав вышеизложенное, мы понимаем, насколько процесс запуска сервиса и интерфейс нестджей соотносятся с данными, но остается еще много нераскрытых деталей, таких как большое количество декораторов (@Get
,@Req
и т. д.), далее будут объяснены модули, обычно используемые Nestjs.
- основные функции
- Контроллер
- Провайдер (бизнес-логика)
- Модуль Полный бизнес-модуль
- NestFactory создает фабричный класс для приложений Nest
- Расширенные возможности
- ПО промежуточного слоя
- Фильтр исключений Фильтр исключений
- Трубка
- Сторожить
- Перехватчик перехватчик
Контроллер, провайдер и модуль были упомянуты выше, поэтому я не буду здесь вдаваться во второе объяснение. NestFactory на самом деле является фабричной функцией, используемой для создания приложения Nestjs. Обычно она создается в файле ввода, который является основным. ts в указанном выше каталоге. Код выглядит следующим образом:
main.ts
import {NestFactory} from '@nestjs/core';
import {AppModule} from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
декоратор декоратор
Декоратор — это часто используемая функция в Nestjs. Он предоставляет несколько общих декораторов для тела запроса. Мы также можем настроить декоратор, и вы можете легко использовать его где угодно.
В дополнение к вышеперечисленным, есть также некоторые декораторы, которые украшают внутренние методы класса, наиболее распространенными из которых являются@Get()
,@Post()
,@Put()
,@Delete()
В ожидании декоратора роутинга я считаю, что большинство внешних интерфейсов могут понять, что это значит, поэтому я не буду их больше объяснять.
ПО промежуточного слоя
Nestjs — это вторичная инкапсуляция Express.Промежуточное ПО в Nestjs эквивалентноПромежуточное ПО в Express, самый распространенный сценарийГлобальное ведение журнала, междоменная обработка, обработка ошибок, форматирование файлов cookieи другие более распространенные сценарии приложений службы API, официальное объяснение выглядит следующим образом:
Промежуточные программы имеют доступ к объекту запроса (REQ), объекта ответа (RES) и следующей функции промежуточного программного обеспечения в цикле запроса / ответа приложения. Следующая функция промежуточного программного обеспечения обычно представлена переменной именем Next.
Для примера возьмем форматирование куки, код модифицированного main.ts выглядит следующим образом:
import {NestFactory} from '@nestjs/core';
import * as cookieParser from 'cookie-parser';
import {AppModule} from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// cookie格式化中间件,经过这个中间件处理,我们就能在req中拿到cookie对象
app.use(cookieParser());
await app.listen(3000);
}
bootstrap();
Фильтр исключений Фильтр исключений
Nestjs имеет встроенный уровень исключений, а встроенный уровень исключений отвечает за обработку всех выброшенных исключений во всем приложении. При обнаружении необработанного исключения конечный пользователь получит дружественный ответ.
В качестве фронтенда мы должны были получать ошибки интерфейса.За выбрасывание ошибок отвечает фильтр исключений.Обычно нашему проекту необходимо настроить формат отчета об ошибках, и сформировать некую спецификацию интерфейса после достижения договоренности с фронт- конец. Встроенный фильтр исключений дает нам формат:
{
"statusCode": 500,
"message": "Internal server error"
}
В общем, такой формат нам не подходит, поэтому нужноНастроить фильтр исключений и привязать к глобальному, давайте сначала реализуем простой фильтр исключений:
Добавляем на базе этого проекта общую папку, в которой хранятся какие-то фильтры, гуарды, пайпы и т.д. Обновленная структура каталогов выглядит следующим образом:
├── app.controller.ts # 控制器
├── app.module.ts # 根模块
├── app.service.ts # 服务
├── common 通用部分
├ ├── filters
├ ├── pipes
├ ├── guards
├ ├── interceptors
├── main.ts # 项目入口,可以选择平台、配置中间件等
└── src 业务模块目录
├── user
├── user.controller.ts
├── user.service.ts
├── user.module.ts
Добавляем файл http-exception.filter.ts в директорию фильтров
http-exception.filter.ts
import {ExceptionFilter, Catch, ArgumentsHost, HttpException} from '@nestjs/common';
import {Response} from 'express';
// 需要Catch()修饰且需要继承ExceptionFilter
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
// 过滤器需要有catch(exception: T, host: ArgumentsHost)方法
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const msg = exception.message;
// 这里对res的处理就是全局错误请求返回的格式
response
.status(status)
.json({
status: status,
code: 1,
msg,
data: null
});
}
}
Далее мы привязываемся к глобалу, снова меняем наш app.module.ts
app.module.ts
import {Module} from '@nestjs/common';
import {APP_FILTER} from '@nestjs/core';
import {AppController} from './app.controller';
import {AppService} from './app.service';
import {HttpExceptionFilter} from './common/filters/http-exception.filter';
import {UsersModule} from './users/users.module';
@Module({
// 引入业务模块
imports: [UsersModule],
controllers: [AppController],
providers: [
// 全局异常过滤器
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
AppService
]
})
export class AppModule {}
Таким образом, наш инициализированный проект имеет собственную обработку исключений.
Трубка
Эту часть трудно понять только по названию, но она хорошо понятна по функциям и сценарию применения.Насколько я понимаю,Каналы — это некоторые обработчики для запроса данных перед обработкой контроллера..
Обычно существует два сценария применения конвейеров:
- запросить преобразование данных
- Запросить проверку данных: проверить входные данные и продолжить, если проверка прошла успешно; выдать исключение, если проверка не пройдена.
Сценариев применения для преобразования данных не так много.Здесь мы говорим только о примере проверки данных.Проверка данных является наиболее распространенным сценарием для проектов среднего и фонового управления.
Обычно наше приложение Nest будет сотрудничатьclass-validatorЧтобы выполнить проверку данных, мы создаем новый файл validation.pipe.ts в каталоге каналов.
validation.pipe.ts
import {PipeTransform, Injectable, ArgumentMetadata, BadRequestException} from '@nestjs/common';
import {validate} from 'class-validator';
import {plainToClass} from 'class-transformer';
// 管道需要@Injectable()修饰,可选择继承Nest内置管道PipeTransform
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
// 管道必须有transform方法,这个方法有两个参数,value :当前处理的参数, metadata:元数据
async transform(value: any, {metatype}: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
Затем мы привязываем этот конвейер глобально, и измененное содержимое app.module.ts выглядит следующим образом:
import {Module} from '@nestjs/common';
import {APP_FILTER, APP_PIPE} from '@nestjs/core';
import {AppController} from './app.controller';
import {AppService} from './app.service';
import {HttpExceptionFilter} from './common/filters/http-exception.filter';
import {ValidationPipe} from './common/pipes/validation.pipe';
import {UsersModule} from './users/users.module';
@Module({
// 引入业务模块
imports: [UsersModule],
controllers: [AppController],
providers: [
// 全局异常过滤器
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
// 全局的数据格式验证管道
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
AppService
]
})
export class AppModule {}
Таким образом, наше приложение добавило функцию проверки данных.Например, когда мы пишем интерфейс, который требует проверки данных, нам нужно сначала создать файл createUser.dto.ts со следующим содержимым:
import {IsString, IsInt} from 'class-validator';
export class CreateUserDto {
@IsString()
name: string;
@IsInt()
age: number;
}
Тогда мыControllerВведение слоя, код выглядит следующим образом:
user.controller.ts
import {Controller, Get, Post, Req, Body} from '@nestjs/common';
import {UserService} from './user.service';
import * as DTO from './createUser.dto';
@Controller('user')
export class UserController {
constructor(
private readonly userService: UserService
) {}
@Get()
findAll(@Req() request) {
return this.userService.findAll(request);
}
// 在这里添加数据校验
@Post()
addUser(@Body() body: DTO.CreateUserDto) {
return this.userService.add(body);
}
}
Если параметр, переданный клиентом, не соответствует спецификации, запрос выдаст ошибку и не продолжит обработку.
Сторожить
Охранники на самом деле являются охранниками маршрутизации, которые защищают интерфейсы, которые мы пишем.Наиболее часто используемые сценарии:Аутентификация интерфейса, у нас обычно есть аутентификация входа для каждого интерфейса бизнес-системы, поэтому обычно мы инкапсулируем глобальную защиту маршрутизации, мы создаем новый auth.guard.ts в каталоге common/guards проекта, код выглядит следующим образом:
auth.guard.ts
import {Injectable, CanActivate, ExecutionContext} from '@nestjs/common';
import {Observable} from 'rxjs';
function validateRequest(req) {
return true;
}
// 守卫需要@Injectable()修饰而且需要继承CanActivate
@Injectable()
export class AuthGuard implements CanActivate {
// 守卫必须有canActivate方法,此方法返回值类型为boolean
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
// 用于鉴权的函数,返回true或false
return validateRequest(request);
}
}
Затем мы привязываем его к глобальному модулю, и измененное содержимое app.module.ts выглядит следующим образом:
import {Module} from '@nestjs/common';
import {APP_FILTER, APP_PIPE, APP_GUARD} from '@nestjs/core';
import {AppController} from './app.controller';
import {AppService} from './app.service';
import {HttpExceptionFilter} from './common/filters/http-exception.filter';
import {ValidationPipe} from './common/pipes/validation.pipe';
import {AuthGuard} from './common/guards/auth.guard';
import {UsersModule} from './users/users.module';
@Module({
// 引入业务模块
imports: [UsersModule],
controllers: [AppController],
providers: [
// 全局异常过滤器
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
// 全局的数据格式验证管道
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
// 全局登录鉴权守卫
{
provide: APP_GUARD,
useClass: AuthGuard,
},
AppService
]
})
export class AppModule {}
Таким образом, наше приложение имеет больше функций глобальной защиты.
Перехватчик перехватчик
Как видно из официальной схемы, перехватчик может перехватывать запросы и ответы, поэтому он делится на перехватчики запросов и перехватчики ответов, многие популярные библиотеки запросов на фронтенде также имеют эту функцию, например axios, umi-request и т.д. Я полагаю, что внешний интерфейс Все учащиеся сталкивались с ним, но на самом деле это программа, которая обрабатывает данные между клиентом и маршрутизатором.
Перехватчики имеют ряд полезных функций, они могут:
- Привязать дополнительную логику до/после выполнения функции
- Преобразование результата, возвращаемого функцией
- Преобразование исключений, выброшенных из функций
- Расширенное базовое поведение функции
- Полностью переписать функцию на основе выбранных условий (например, для целей кэширования).
Затем мы реализуем перехватчик ответа для форматирования данных глобального ответа и создаем новый файл res.interceptors.ts в каталоге /common/interceptors со следующим содержимым:
res.interceptors.ts
import {Injectable, NestInterceptor, ExecutionContext, CallHandler} from '@nestjs/common';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
export interface Response<T> {
code: number;
data: T;
}
@Injectable()
export class ResInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => {
const ctx = context.switchToHttp();
const response = ctx.getResponse();
response.status(200);
const res = this.formatResponse(data) as any;
return res;
}));
}
formatResponse<T>(data: any): Response<T> {
return {code: 0, data};
}
}
Роль этого охранника ответа состоит в том, чтобы отформатировать данные, возвращаемые нашим интерфейсом, в{code, data}
Format, то нам нужно привязать этот гард к глобалу, измененное содержимое app.module.ts выглядит следующим образом:
import {Module} from '@nestjs/common';
import {APP_FILTER, APP_PIPE, APP_GUARD, APP_INTERCEPTOR} from '@nestjs/core';
import {AppController} from './app.controller';
import {AppService} from './app.service';
import {HttpExceptionFilter} from './common/filters/http-exception.filter';
import {ValidationPipe} from './common/pipes/validation.pipe';
import {AuthGuard} from './common/guards/auth.guard';
import {ResInterceptor} from './common/interceptors/res.interceptors';
import {UsersModule} from './users/users.module';
@Module({
// 引入业务模块
imports: [UsersModule],
controllers: [AppController],
providers: [
// 全局异常过滤器
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
// 全局的数据格式验证管道
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
// 全局登录鉴权守卫
{
provide: APP_GUARD,
useClass: AuthGuard,
},
// 全局响应拦截器
{
provide: APP_INTERCEPTOR,
useClass: ResInterceptor,
},
AppService
]
})
export class AppModule {}
Таким образом фиксируется формат ответа всех интерфейсов нашего приложения.
Краткое содержание Nestjs
После серии шагов, описанных выше, мы построили небольшое приложение (без журналов и источников данных), тогда возникает вопрос, как приложение, которое мы реализуем, обрабатывает и отвечает на данные шаг за шагом после того, как интерфейс инициирует запрос? Действуйте следующим образом:
Клиентский запрос -> промежуточное ПО промежуточного слоя -> Guard Guard -> перехватчик запросов (у нас его нет) -> Pipe Pipeline -> функция обработки маршрутизации уровня Controllor -> перехватчик ответов -> ответ клиента
Функция обработки маршрутизации уровня Controller будет вызывать Provider, а Provider отвечает за получение базовых данных и обработку бизнес-логики; фильтр исключений будет выполняться после того, как программа выдаст ошибку.
Суммировать
После вышеизложенного мы можем получить базовое представление о концепции уровня BFF и выполнить шаги для создания небольшого приложения Nestjs, но все еще существует большой разрыв с приложениями корпоративного уровня.
Приложениям уровня предприятия также необходим доступ к источникам данных (данные внутреннего интерфейса, данные базы данных, данные конфигурации apollo), журналам, ссылкам, кэшированию, мониторингу и другим важным функциям.
- Для подключения к уровню BFF требуется надежная инфраструктура и подходящие бизнес-сценарии.
- Nestjs реализован на основе Express и относится к дизайнерской идее springboot.Начать работу очень просто.Чтобы быть профессионалом, нужно понимать его принципы, особенновнедрение зависимостидизайн-мышление