От Nest к Nesk — практика модульной структуры Node.

Node.js внешний интерфейс Express koa
От Nest к Nesk — практика модульной структуры Node.

Текст: Дафу (архитектор веб-интерфейса Hujiang)

Эта статья является оригинальной, перейдите в Hujiang Technology

Сначала перейдите по адресу проекта (:>):

Гнездо:github.com/nestjs/nest

Неск:GitHub.com/can-Yoko-place/what…

Первое знакомство с Нестом

Nest — это экспресс-фреймворк для узлов, вдохновленный angular.Согласно официальному веб-сайту, он предназначен для предоставления готовой архитектуры приложений, которая позволяет легко создавать легко тестируемые, расширяемые, слабосвязанные и простые -поддерживать приложения.программа.

Хотя он глубоко вдохновлен angular на уровне дизайна, он на самом деле похож на знакомую архитектуру Java Spring с точки зрения внутренней разработки.Он использует много навыков аспектного программирования, а затем полностью разделяет задачи с помощью комбинации декораторов. . При этом в качестве основного языка разработки используется Typescript (также поддерживает Javascript), что обеспечивает надежность всей серверной системы.

Мощная архитектура Nest

Итак, зачем вам вообще нужен фреймворк Nest?В прошлом году мы начали использовать Node в больших масштабах, чтобы заменить первоначальную внутреннюю разработку слоя просмотра, дав фронтенд-разработке возможность разделить фронтенд и бэкенд. -конец, кроме СПА. В первые дни работа уровня Node была очень простой — рендеринг интерфейса прокси страницы, но при постепенном использовании люди будут уделять больше поддержки слою Node, особенно в некоторых внутренних проектах, вы просите бэкэнд обернуть некоторые существующие интерфейсы SOA. , другая сторона часто не желает. Затем мы будем вынуждены заниматься дополнительными делами на уровне Node, включая, помимо прочего, комбинированную упаковку данных, проверку разрешений запросов, проверку данных запроса и т. д. В первые дни наша структура была самой традиционной MVC. архитектуры, но мы просматривая бизнес-код, часто оказываемся сложными и трудными для поддержки кода уровня контроллера (от проверки разрешений до рендеринга страницы и до самого конца :)).

Итак, давайте посмотрим, что теперь может сделать Nest? Начнем с самого простого официального примера:

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

Здесь запускается вложенный экземпляр. Давайте сначала не будем смотреть на ValidationPipe, а посмотрим на содержимое ApplicationModule:

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewaresConsumer): void {
    consumer
      .apply(LoggerMiddleware)
      .with('ApplicationModule')
      .forRoutes(CatsController);
  }
}
@Module({
  controllers: [CatsController],
  components: [CatsService],
})
export class CatsModule {}

Здесь мы видим начальный модуль Nest первого уровня, который является основой модульной разработки.Все контроллеры, компоненты и т. д. могут быть разделены на определенный модуль в соответствии с бизнесом, а затем модули могут быть вложены, чтобы стать законченным Система, позаимствовать официальную фотографию Zhang Nest:

Концепция компонента в гнезде - это фактически все объекты, которые могут быть внедрены.Концепция внедрения зависимостей здесь подробно не объясняется.Можно понять, что разработчику не нужно создавать экземпляр класса, а фреймворк создаст его и сохранит. как синглтон для использования.

@Controller('cats')
@UseGuards(RolesGuard)
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  @Roles('admin')
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }

  @Get(':id')
  findOne(
    @Param('id', new ParseIntPipe())
    id,
  ): Promise<Cat> {
    return this.catsService.findOne(id);
  }
}

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

@Guard()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(request, context: ExecutionContext): boolean {
    const { parent, handler } = context;
    const roles = this.reflector.get<string[]>('roles', handler);
    if (!roles) {
      return true;
    }

    // 自行实现
  }
}

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

Перехватчики — это перехватчики, которые могут:

  • Привязать дополнительную логику до/после выполнения метода
  • Преобразование результата, возвращаемого функцией
  • Преобразование исключений, выброшенных из функций
  • Полностью переписать функцию на основе выбранных условий (например, для целей кэширования).

В этом примере есть два перехватчика, один используется для записи времени выполнения функции, а другой используется для переноса результата.Эти два требования очень распространены в разработке, и перехватчик предоставит поток наблюдателя rxjs для возврата функции обработки. и поддерживает асинхронные функции. Мы можем использовать map() для изменения результата этого потока, и мы можем использовать оператор do для наблюдения за статусом выполнения функции и наблюдения за последовательностью. Кроме того, мы можем предотвратить выполнение функции не возвращая поток. Пример LoggingInterceptor выглядит следующим образом:

@Interceptor()
export class LoggingInterceptor implements NestInterceptor {
  intercept(dataOrRequest, context: ExecutionContext, stream$: Observable<any>): Observable<any> {
    console.log('Before...');
    const now = Date.now();

    return stream$.do(
      () => console.log(`After... ${Date.now() - now}ms`),
    );
  }
}

Возвращаясь к исходному ValidationPipe, это мощный инструмент проверки, мы видим, что в операции вставки в предыдущем коде контроллера есть CreateCatDto, dto — это объект передачи данных, dto можно определить так:

export class CreateCatDto {
  @IsString() readonly name: string;

  @IsInt() readonly age: number;

  @IsString() readonly breed: string;
}

Затем ValidationPipe проверит, соответствует ли тело этому dto, и если нет, выполнит схему обработки, которую вы установили в пайпе. Как этого добиться можно написать в другой статье, так что рекомендую к прочтениюгнездо китайский гид(Кстати, спасибо студентам, которые перевели)

Полный код примера можно увидеть01-cats-app

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

Nesk — попытка десантного решения

Хотя лично мне Nest очень нравится, но у нас в компании уже есть зрелый фреймворк Aconite на основе koa2, а Nest на основе экспресса, я проверял исходники Nest и есть определенная зависимость от экспресса, но и koa2 и экспресс поддерживают асинхронный синтаксис После этого разница находится в пределах контролируемого диапазона. Кроме того, Nest принимает экземпляр Express. В nesk нам нужно только настроить его на экземпляр koa, тогда это также может быть любой экземпляр проекта, который наследуется от koa. Наш фреймворк также является фреймворком node, унаследованным от koa в версии 2.0. Исходя из этого, нам нужен только простой уровень адаптера для беспрепятственного подключения Aconite к nesk, что сокращает объединение nesk и внутренних служб и обеспечивает интеграцию всех общедоступных внутренних служб в Aconite. Nest — это просто более совершенная парадигма разработки для нас, и она не берет на себя никаких общих модулей.

Итак, необходимую нам работу можно свести к следующему:

  1. Поддержка Коа
  2. Адаптироваться к акониту

Поддержка Koa Мы внесли небольшие изменения на основе Nest, чтобы сделать Nesk совместимым с системой Koa. Нам нужно только завершить слой адаптера между Nesk и Aconite, чтобы завершить посадку Nesk, и код при окончательном запуске станет таким:

import { NeskFactory } from '@neskjs/core';
import { NeskAconite } from '@hujiang/nesk-aconite';
import { ApplicationModule } from './app.module';
import { config } from './common/config';
import { middwares } from './common/middlware';

async function bootstrap() {
  const server = new NeskAconite({
    projectRoot: __dirname,
    middlewares,
    config
  });
  const app = await NeskFactory.create(ApplicationModule, server);
  await app.listen(config.port);
}

Наконец, у Nest есть много пакетов в области @nest, что удобно для некоторых инструментов для доступа к Nest.Если они не имеют ничего общего с экспрессом, мы можем использовать их напрямую. Однако пакет часто зависит от @nest/common или @nesk/core.Здесь вы можете использовать псевдоним модуля для выполнения редиректа (вы можете попробовать пример graphql):

"_moduleAliases": {
  "@nestjs/common": "node_modules/@neskjs/common",
  "@nestjs/core": "node_modules/@neskjs/core"
}

адрес НескаNesk, мы сделали базовый тест процесса для Nesk, который охватывает общие и основные, и другие ждут улучшения.Все разработчики, которые готовы внести изменения, приветствуются.

Неадекватность и ожидания

На самом деле, с лучшей точки зрения, мы должны позволить Nest принимать различные базовые фреймворки, то есть мы можем использовать Express или koa и сглаживать различия через слой адаптера. Однако стоимость этой модификации будет выше.

С другой стороны, у Nest есть свои недостатки: в плане внедрения зависимостей по-прежнему выбирается ReflectiveInjector, а Angular начал использовать StaticInjector. почему мы решили разделить Fork? причина, по которой nesk внесла более смелые изменения во внутренний код. Кроме того, внедрение зависимостей в Angular мощнее, и есть такие функции, как useFactory и deps, удобные для замены тестов, которые нужно дополнять через nest.

Наконец, все фреймворки на основе Koa зададут вопрос, а совместимы ли они с eggjs(:)), ведь и Nest, и Nesk являются фреймворком для обязательных спецификаций разработки, пока eggjs все еще строится на основе koa. , его можно завершить Интеграция просто в том, что eggjs сильно изменился на уровне стартапа, и есть много различий между парадигмой разработки и веткой, и интеграция двух не имеет существенных преимуществ.

Короче говоря, узел - это относительно гибкий метод разработки.


В 2019 году оригинальная новая книга iKcamp «Практика разработки Koa и Node.js» была продана на JD.com, Tmall, Amazon и Dangdang!