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() 装饰器为核心,将应用拆分为高内聚、低耦合的领域模块。每个模块封装一组相关的控制器、服务、实体和数据库访问逻辑。
下图展示了典型的事务边界治理项目中的模块化分层结构:
每个领域模块通过 @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的实例化与事务边界:
常见陷阱: 如果事务性Service使用默认单例作用域,多个并发请求会共享同一个Service实例。当Service内部持有数据库连接或事务上下文时,会导致事务交叉、连接泄漏甚至死锁。务必为涉及事务的Service显式声明
@Injectable({ scope: Scope.REQUEST })。
3.4 事务治理的依赖注入最佳实践
基于上述分析,在项目初始化阶段就应建立以下DI与事务治理规范:
- 事务性Service使用REQUEST作用域:确保每个请求拥有独立的事务上下文。
- Repository保持单例:Repository是无状态的数据库访问对象,单例即可。
- 事务管理器集中管理:通过自定义
@Transactional()装饰器或拦截器统一管理事务边界。 - 避免在构造函数中执行数据库操作:构造函数在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);
}
}