3. NestJS项目初始化:使用CLI创建项目、模块化架构概览、依赖注入与事务的关系

本章是课程的技术起点。我们将通过NestJS CLI创建一个标准项目骨架,并以此为基础,剖析模块化架构的核心要素,最后建立依赖注入(DI)与数据库事务治理之间的关键联系。理解这三者的关系,是后续设计事务边界契约的前提。

3.1 使用CLI创建项目与目录结构

NestJS提供了强大的命令行工具,用于快速搭建项目并生成模块化代码。执行以下命令初始化一个名为 transaction-boundary-api 的项目:

npm i -g @nestjs/cli
nest new transaction-boundary-api --package-manager npm
cd transaction-boundary-api

创建完成后,项目核心目录结构如下:

目录/文件 职责 事务治理关注点
src/ 应用源代码根目录 所有业务逻辑与事务边界定义所在
src/main.ts 应用入口,启动HTTP服务 全局管道、过滤器、拦截器注册点
src/app.module.ts 根模块,聚合所有子模块 全局事务拦截器、数据库连接配置
src/<domain>/ 领域模块目录(如 order/) 每个模块独立管理自身事务边界
src/common/ 共享基础设施(过滤器、拦截器、装饰器) 事务拦截器、事务装饰器存放位置
实践建议: 在项目初始化后,立即在 src/common 下创建 interceptors、decorators、filters 三个子目录,为后续事务治理基础设施预留位置。

3.2 模块化架构概览:从根模块到领域模块

NestJS的模块化架构以 @Module() 装饰器为核心,将应用拆分为高内聚、低耦合的领域模块。每个模块封装一组相关的控制器、服务、实体和数据库访问逻辑。

下图展示了典型的事务边界治理项目中的模块化分层结构:

NestJS 模块化架构与事务边界分层 AppModule (根模块) OrderModule 订单领域 PaymentModule 支付领域 InventoryModule 库存领域 基础设施层:TypeORM / Prisma / Mongoose PostgreSQL / MySQL (事务边界) ⚠ 事务边界治理:每个领域模块的 Service 层负责定义事务的开启、提交与回滚

每个领域模块通过 @Module() 装饰器声明其内部的组件:

// src/order/order.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderController } from './order.controller';
import { OrderService } from './order.service';
import { Order } from './entities/order.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Order])],
  controllers: [OrderController],
  providers: [OrderService],
  exports: [OrderService], // 供其他模块使用
})
export class OrderModule {}
模块化设计原则: 每个领域模块只暴露必要的 Service 接口,内部数据库操作和事务逻辑对外部完全封装。这种隔离是事务边界治理的第一道防线。

3.3 依赖注入与事务的关系

NestJS的依赖注入(DI)系统是事务治理的技术基石。理解DI如何影响事务的生命周期,是避免“事务失效”或“连接泄漏”等生产事故的关键。

下表总结了DI作用域与事务行为的对应关系:

DI作用域 实例生命周期 事务行为影响 适用场景
DEFAULT(单例) 应用启动时创建,全局共享 事务上下文可能被多个请求复用,需谨慎管理 无状态Service、Repository
REQUEST 每个HTTP请求创建新实例 天然隔离事务上下文,推荐用于事务性Service 需要事务管理的业务Service
TRANSIENT 每次注入时创建新实例 事务上下文完全独立,但开销较大 工具类、辅助类

在事务治理中,REQUEST作用域是最安全的选择。它确保每个HTTP请求拥有独立的事务上下文,避免单例Service中因并发请求导致的事务交叉污染。

下图展示了DI容器如何管理事务性Service的实例化与事务边界:

依赖注入容器与事务上下文管理 DI 容器 UserService (单例) OrderService (REQUEST) TransactionManager HTTP 请求 #1 → 创建独立事务上下文 → 注入 OrderService 实例 → 事务边界:A HTTP 请求 #2 → 创建独立事务上下文 → 注入 OrderService 实例 → 事务边界:B 数据库连接池
常见陷阱: 如果事务性Service使用默认单例作用域,多个并发请求会共享同一个Service实例。当Service内部持有数据库连接或事务上下文时,会导致事务交叉、连接泄漏甚至死锁。务必为涉及事务的Service显式声明 @Injectable({ scope: Scope.REQUEST })。

3.4 事务治理的依赖注入最佳实践

基于上述分析,在项目初始化阶段就应建立以下DI与事务治理规范:

  1. 事务性Service使用REQUEST作用域:确保每个请求拥有独立的事务上下文。
  2. Repository保持单例:Repository是无状态的数据库访问对象,单例即可。
  3. 事务管理器集中管理:通过自定义 @Transactional() 装饰器或拦截器统一管理事务边界。
  4. 避免在构造函数中执行数据库操作:构造函数在DI实例化时执行,此时事务上下文可能尚未就绪。
// 推荐的事务性Service声明方式
import { Injectable, Scope } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Order } from './entities/order.entity';

@Injectable({ scope: Scope.REQUEST }) // 每个请求独立实例
export class OrderService {
  constructor(
    @InjectRepository(Order)
    private readonly orderRepo: Repository<Order>,
  ) {}

  async createOrder(data: Partial<Order>): Promise<Order> {
    // 事务边界在此方法内通过 @Transactional 或 queryRunner 管理
    return this.orderRepo.save(data);
  }
}