04-Service层设计模式
分类:elysia
发布于:
阅读时间:106 分钟
Service层设计模式规范
概述
本文档定义了 Elysia 项目中 Service 层的设计模式和最佳实践。Service 层负责业务逻辑处理、数据操作和业务规则实现,是应用的核心业务层。
Service层职责
核心职责
- 业务逻辑处理: 实现具体的业务规则和逻辑
- 数据操作: 使用 Drizzle ORM 进行数据库操作
- 事务管理: 处理复杂的事务性操作
- 错误处理: 抛出具体的业务错误
- 数据转换: 必要时进行数据格式转换
设计原则
- 单一职责: 每个Service专注于一个业务领域
- 依赖注入: 通过构造函数或装饰器注入依赖
- 错误优先: 抛出错误而不是返回null
- 类型安全: 使用TypeScript确保类型安全
基础Service结构
1. 标准Service类模板
// users.service.ts
import { eq, and, or, like, count, getTableColumns } from "drizzle-orm";
import { db } from "@/db/connection";
import { usersTable } from "@/db/schema/users.schema";
import {
CreateUserDto,
UpdateUserDto,
UserListQueryDto,
UserEntity
} from "./users.model";
import {
NotFoundError,
ConflictError,
DatabaseError
} from "@/utils/errors";
/**
* 用户服务类
* 处理用户相关的所有业务逻辑
*/
export class UserService {
/**
* 获取表的安全列(排除敏感字段)
*/
private readonly safeColumns = (() => {
const { password, resetToken, verificationToken, ...safeColumns } =
getTableColumns(usersTable);
return safeColumns;
})();
/**
* 根据ID获取用户
* @param id 用户ID
* @returns 用户实体信息
* @throws {NotFoundError} 当用户不存在时
*/
async getUserById(id: number): Promise<UserEntity> {
const [user] = await db
.select(this.safeColumns)
.from(usersTable)
.where(eq(usersTable.id, id))
.limit(1);
if (!user) {
throw new NotFoundError(`User with id ${id} not found`);
}
return user;
}
/**
* 根据邮箱获取用户
* @param email 用户邮箱
* @returns 用户实体信息
* @throws {NotFoundError} 当用户不存在时
*/
async getUserByEmail(email: string): Promise<UserEntity> {
const [user] = await db
.select(this.safeColumns)
.from(usersTable)
.where(eq(usersTable.email, email))
.limit(1);
if (!user) {
throw new NotFoundError(`User with email ${email} not found`);
}
return user;
}
/**
* 创建用户
* @param userData 用户创建数据
* @returns 创建后的用户实体
* @throws {ConflictError} 当邮箱或用户名已存在时
*/
async createUser(userData: CreateUserDto): Promise<UserEntity> {
try {
const [user] = await db
.insert(usersTable)
.values({
...userData,
createdAt: new Date(),
updatedAt: new Date(),
})
.returning(this.safeColumns);
return user;
} catch (error: any) {
if (error.code === '23505') { // PostgreSQL唯一约束冲突
throw new ConflictError('User with this email or username already exists');
}
throw new DatabaseError('Failed to create user');
}
}
/**
* 更新用户信息
* @param id 用户ID
* @param updateData 更新数据
* @returns 更新后的用户实体
* @throws {NotFoundError} 当用户不存在时
*/
async updateUser(id: number, updateData: UpdateUserDto): Promise<UserEntity> {
// 先检查用户是否存在
await this.getUserById(id);
try {
const [user] = await db
.update(usersTable)
.set({
...updateData,
updatedAt: new Date(),
})
.where(eq(usersTable.id, id))
.returning(this.safeColumns);
return user;
} catch (error: any) {
if (error.code === '23505') {
throw new ConflictError('Email or username already exists');
}
throw new DatabaseError('Failed to update user');
}
}
/**
* 删除用户
* @param id 用户ID
* @throws {NotFoundError} 当用户不存在时
*/
async deleteUser(id: number): Promise<void> {
// 先检查用户是否存在
await this.getUserById(id);
try {
await db.delete(usersTable).where(eq(usersTable.id, id));
} catch (error: any) {
throw new DatabaseError('Failed to delete user');
}
}
/**
* 获取用户列表(分页)
* @param query 查询参数
* @returns 分页用户列表
*/
async getUserList(query: UserListQueryDto): Promise<{
items: UserEntity[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}> {
const {
page = 1,
limit = 10,
search,
isActive,
sort = 'createdAt',
order = 'desc'
} = query;
const offset = (page - 1) * limit;
// 构建查询条件
const conditions = [];
if (search) {
conditions.push(
or(
like(usersTable.username, `%${search}%`),
like(usersTable.email, `%${search}%`),
like(usersTable.firstName, `%${search}%`),
like(usersTable.lastName, `%${search}%`)
)
);
}
if (isActive !== undefined) {
conditions.push(eq(usersTable.isActive, isActive));
}
// 构建查询
let queryBuilder = db
.select(this.safeColumns)
.from(usersTable);
let totalBuilder = db
.select({ total: count() })
.from(usersTable);
if (conditions.length > 0) {
const whereCondition = and(...conditions);
queryBuilder = queryBuilder.where(whereCondition);
totalBuilder = totalBuilder.where(whereCondition);
}
// 添加排序
const orderByColumn = usersTable[sort as keyof typeof usersTable];
if (orderByColumn) {
queryBuilder = queryBuilder.orderBy(
order === 'asc' ?
asc(orderByColumn) :
desc(orderByColumn)
);
}
// 执行查询
const [users, [{ total }]] = await Promise.all([
queryBuilder.limit(limit).offset(offset),
totalBuilder
]);
return {
items: users,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit)
}
};
}
/**
* 搜索用户
* @param searchTerm 搜索关键词
* @param limit 结果数量限制
* @returns 匹配的用户列表
*/
async searchUsers(searchTerm: string, limit: number = 10): Promise<UserEntity[]> {
if (!searchTerm || searchTerm.length < 2) {
return [];
}
const users = await db
.select(this.safeColumns)
.from(usersTable)
.where(
and(
or(
like(usersTable.username, `%${searchTerm}%`),
like(usersTable.email, `%${searchTerm}%`),
like(usersTable.firstName, `%${searchTerm}%`),
like(usersTable.lastName, `%${searchTerm}%`)
),
eq(usersTable.isActive, true)
)
)
.limit(limit);
return users;
}
/**
* 激活/停用用户
* @param id 用户ID
* @param isActive 激活状态
* @throws {NotFoundError} 当用户不存在时
*/
async toggleUserStatus(id: number, isActive: boolean): Promise<UserEntity> {
return this.updateUser(id, { isActive });
}
/**
* 验证用户邮箱
* @param email 用户邮箱
* @returns 是否验证通过
*/
async validateUserEmail(email: string): Promise<boolean> {
try {
await this.getUserByEmail(email);
return true;
} catch (error) {
if (error instanceof NotFoundError) {
return false;
}
throw error;
}
}
}
2. 复杂业务Service示例
// order.service.ts
import { eq, and, sum, getTableColumns } from "drizzle-orm";
import { db } from "@/db/connection";
import {
ordersTable,
orderItemsTable,
productsTable,
usersTable
} from "@/db/schema";
import {
CreateOrderDto,
OrderDetailDto,
OrderStatus
} from "./orders.model";
import {
NotFoundError,
ValidationError,
BusinessLogicError
} from "@/utils/errors";
/**
* 订单服务类
* 处理订单相关的复杂业务逻辑
*/
export class OrderService {
/**
* 创建订单(包含事务处理)
* @param orderData 订单创建数据
* @returns 创建后的订单详情
*/
async createOrder(orderData: CreateOrderDto): Promise<OrderDetailDto> {
return await db.transaction(async (tx) => {
// 1. 验证用户存在
const [user] = await tx
.select()
.from(usersTable)
.where(eq(usersTable.id, orderData.userId))
.limit(1);
if (!user) {
throw new NotFoundError('User not found');
}
// 2. 验证商品库存并计算总价
let totalAmount = 0;
const orderItems: any[] = [];
for (const item of orderData.items) {
const [product] = await tx
.select()
.from(productsTable)
.where(eq(productsTable.id, item.productId))
.limit(1);
if (!product) {
throw new NotFoundError(`Product ${item.productId} not found`);
}
if (product.stock < item.quantity) {
throw new ValidationError(
`Insufficient stock for product ${product.name}. Available: ${product.stock}`
);
}
const itemTotal = Number(product.price) * item.quantity;
totalAmount += itemTotal;
orderItems.push({
productId: item.productId,
quantity: item.quantity,
unitPrice: product.price,
totalPrice: itemTotal.toString()
});
// 3. 扣减库存
await tx
.update(productsTable)
.set({
stock: product.stock - item.quantity,
updatedAt: new Date()
})
.where(eq(productsTable.id, item.productId));
}
// 4. 创建订单
const [order] = await tx
.insert(ordersTable)
.values({
userId: orderData.userId,
totalAmount: totalAmount.toString(),
status: OrderStatus.PENDING,
createdAt: new Date(),
updatedAt: new Date(),
})
.returning();
// 5. 创建订单项
const itemsWithOrderId = orderItems.map(item => ({
...item,
orderId: order.id,
}));
await tx.insert(orderItemsTable).values(itemsWithOrderId);
// 6. 返回订单详情
return this.getOrderDetailById(order.id);
});
}
/**
* 获取订单详情(包含关联数据)
* @param orderId 订单ID
* @returns 订单详情
*/
async getOrderDetailById(orderId: number): Promise<OrderDetailDto> {
const [order] = await db
.select({
...getTableColumns(ordersTable),
user: {
id: usersTable.id,
username: usersTable.username,
email: usersTable.email,
},
items: sql`json_agg(
json_build_object(
'id', ${orderItemsTable.id},
'productId', ${orderItemsTable.productId},
'productName', ${productsTable.name},
'quantity', ${orderItemsTable.quantity},
'unitPrice', ${orderItemsTable.unitPrice},
'totalPrice', ${orderItemsTable.totalPrice}
)
)`.mapWith(OrderItemsSchema.array()),
})
.from(ordersTable)
.leftJoin(usersTable, eq(ordersTable.userId, usersTable.id))
.leftJoin(orderItemsTable, eq(ordersTable.id, orderItemsTable.orderId))
.leftJoin(productsTable, eq(orderItemsTable.productId, productsTable.id))
.where(eq(ordersTable.id, orderId))
.groupBy(ordersTable.id, usersTable.id)
.limit(1);
if (!order) {
throw new NotFoundError('Order not found');
}
return order;
}
/**
* 更新订单状态
* @param orderId 订单ID
* @param status 新状态
* @returns 更新后的订单
*/
async updateOrderStatus(orderId: number, status: OrderStatus): Promise<OrderDetailDto> {
// 检查订单是否存在
const existingOrder = await this.getOrderDetailById(orderId);
// 业务规则验证
this.validateOrderStatusTransition(existingOrder.status, status);
try {
await db
.update(ordersTable)
.set({
status,
updatedAt: new Date()
})
.where(eq(ordersTable.id, orderId));
return this.getOrderDetailById(orderId);
} catch (error: any) {
throw new DatabaseError('Failed to update order status');
}
}
/**
* 取消订单(包含库存回滚)
* @param orderId 订单ID
* @returns 取消后的订单
*/
async cancelOrder(orderId: number): Promise<OrderDetailDto> {
return await db.transaction(async (tx) => {
const order = await this.getOrderDetailById(orderId);
// 只有待处理的订单可以取消
if (order.status !== OrderStatus.PENDING) {
throw new BusinessLogicError('Only pending orders can be cancelled');
}
// 回滚库存
for (const item of order.items) {
await tx
.update(productsTable)
.set({
stock: sql`${productsTable.stock} + ${item.quantity}`,
updatedAt: new Date()
})
.where(eq(productsTable.id, item.productId));
}
// 更新订单状态
await tx
.update(ordersTable)
.set({
status: OrderStatus.CANCELLED,
updatedAt: new Date()
})
.where(eq(ordersTable.id, orderId));
return this.getOrderDetailById(orderId);
});
}
/**
* 验证订单状态转换的合法性
* @param currentStatus 当前状态
* @param newStatus 新状态
*/
private validateOrderStatusTransition(
currentStatus: OrderStatus,
newStatus: OrderStatus
): void {
const validTransitions = {
[OrderStatus.PENDING]: [OrderStatus.CONFIRMED, OrderStatus.CANCELLED],
[OrderStatus.CONFIRMED]: [OrderStatus.SHIPPED, OrderStatus.CANCELLED],
[OrderStatus.SHIPPED]: [OrderStatus.DELIVERED],
[OrderStatus.DELIVERED]: [],
[OrderStatus.CANCELLED]: [],
};
if (!validTransitions[currentStatus].includes(newStatus)) {
throw new BusinessLogicError(
`Cannot transition from ${currentStatus} to ${newStatus}`
);
}
}
}
Service层最佳实践
1. 错误处理策略
// ✅ 正确:使用具体的错误类型
class UserService {
async getUserById(id: number): Promise<UserEntity> {
const user = await db
.select()
.from(usersTable)
.where(eq(usersTable.id, id))
.limit(1);
if (!user.length) {
throw new NotFoundError(`User with id ${id} not found`);
}
return user[0];
}
async createUser(userData: CreateUserDto): Promise<UserEntity> {
try {
const [user] = await db.insert(usersTable).values(userData).returning();
return user;
} catch (error: any) {
// 处理特定的数据库错误
switch (error.code) {
case '23505': // 唯一约束冲突
throw new ConflictError('User already exists');
case '23503': // 外键约束冲突
throw new ValidationError('Invalid reference data');
case '23502': // 非空约束冲突
throw new ValidationError('Required field is missing');
default:
throw new DatabaseError('Failed to create user');
}
}
}
}
// ❌ 错误:返回null或undefined
async getUserById(id: number): Promise<UserEntity | null> {
const user = await db.select().from(usersTable).where(eq(usersTable.id, id)).limit(1);
return user[0] || null; // 应该抛出错误
}
2. 分页查询标准模式
// ✅ 正确:标准分页实现
async getList(query: ListQueryDto): Promise<{
items: T[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}> {
const { page = 1, limit = 10, search, filters } = query;
const offset = (page - 1) * limit;
// 构建查询条件
const conditions = this.buildQueryConditions(search, filters);
// 并行执行数据和总数查询
const [items, [{ total }]] = await Promise.all([
db
.select()
.from(this.table)
.where(and(...conditions))
.limit(limit)
.offset(offset)
.orderBy(desc(this.table.createdAt)),
db
.select({ total: count() })
.from(this.table)
.where(and(...conditions))
]);
return {
items,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit)
}
};
}
// ✅ 正确:动态条件构建
private buildQueryConditions(search?: string, filters?: any): any[] {
const conditions = [];
if (search) {
conditions.push(
or(
like(this.table.name, `%${search}%`),
like(this.table.description, `%${search}%`)
)
);
}
if (filters?.status) {
conditions.push(eq(this.table.status, filters.status));
}
if (filters?.dateFrom) {
conditions.push(gte(this.table.createdAt, new Date(filters.dateFrom)));
}
if (filters?.dateTo) {
conditions.push(lte(this.table.createdAt, new Date(filters.dateTo)));
}
return conditions;
}
3. 数据验证和转换
// ✅ 正确:Service层数据转换
class ProductService {
async createProduct(productData: CreateProductDto): Promise<ProductEntity> {
// 业务数据验证
this.validateProductData(productData);
// 数据转换
const transformedData = {
...productData,
price: productData.price.toString(), // Decimal类型转换
isActive: productData.isActive ?? true, // 默认值设置
slug: this.generateSlug(productData.name), // 自动生成字段
};
try {
const [product] = await db
.insert(productsTable)
.values(transformedData)
.returning();
return product;
} catch (error: any) {
this.handleDatabaseError(error);
}
}
private validateProductData(data: CreateProductDto): void {
if (data.price <= 0) {
throw new ValidationError('Product price must be greater than 0');
}
if (data.stock < 0) {
throw new ValidationError('Product stock cannot be negative');
}
if (!data.name || data.name.trim().length === 0) {
throw new ValidationError('Product name is required');
}
}
private generateSlug(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
private handleDatabaseError(error: any): never {
switch (error.code) {
case '23505':
throw new ConflictError('Product with this name already exists');
default:
throw new DatabaseError('Failed to create product');
}
}
}
4. 事务处理模式
// ✅ 正确:复杂事务处理
class OrderService {
async processOrder(orderId: number): Promise<OrderDetailDto> {
return await db.transaction(async (tx) => {
// 1. 获取并锁定订单
const [order] = await tx
.select()
.from(ordersTable)
.where(eq(ordersTable.id, orderId))
.for('update') // 行级锁
.limit(1);
if (!order) {
throw new NotFoundError('Order not found');
}
// 2. 验证订单状态
if (order.status !== OrderStatus.PENDING) {
throw new BusinessLogicError('Order cannot be processed');
}
// 3. 扣减库存
await this.deductInventory(tx, order.id);
// 4. 更新订单状态
await tx
.update(ordersTable)
.set({
status: OrderStatus.PROCESSING,
updatedAt: new Date()
})
.where(eq(ordersTable.id, orderId));
// 5. 创建库存变动记录
await this.createInventoryLog(tx, order.id);
// 6. 返回更新后的订单
return this.getOrderDetailById(orderId);
});
}
private async deductInventory(tx: any, orderId: number): Promise<void> {
const orderItems = await tx
.select()
.from(orderItemsTable)
.where(eq(orderItemsTable.orderId, orderId));
for (const item of orderItems) {
const [product] = await tx
.select()
.from(productsTable)
.where(eq(productsTable.id, item.productId))
.for('update')
.limit(1);
if (product.stock < item.quantity) {
throw new ValidationError(`Insufficient stock for product ${product.name}`);
}
await tx
.update(productsTable)
.set({
stock: product.stock - item.quantity,
updatedAt: new Date()
})
.where(eq(productsTable.id, item.productId));
}
}
}
Service层依赖注入
1. 基础依赖注入
// ✅ 正确:构造函数注入
export class UserService {
constructor(
private readonly db: DrizzleDB,
private readonly emailService: EmailService,
private readonly cacheService: CacheService,
private readonly logger: Logger
) {}
async createUser(userData: CreateUserDto): Promise<UserEntity> {
try {
const user = await this.db.insert(usersTable).values(userData).returning();
// 发送欢迎邮件
await this.emailService.sendWelcomeEmail(user.email);
// 清除相关缓存
await this.cacheService.delete(`users:list`);
this.logger.info('User created successfully', { userId: user.id });
return user;
} catch (error) {
this.logger.error('Failed to create user', { error, userData });
throw error;
}
}
}
// ✅ 正确:使用装饰器注入(如果支持)
export class ProductService {
@Inject('database')
private readonly db: DrizzleDB;
@Inject('cache')
private readonly cache: CacheService;
async getProductById(id: number): Promise<ProductEntity> {
const cacheKey = `product:${id}`;
// 尝试从缓存获取
let product = await this.cache.get(cacheKey);
if (!product) {
product = await this.db
.select()
.from(productsTable)
.where(eq(productsTable.id, id))
.limit(1);
if (product) {
// 缓存5分钟
await this.cache.set(cacheKey, product, { ttl: 300 });
}
}
return product;
}
}
2. Service工厂模式
// services/factory/user-service.factory.ts
export class UserServiceFactory {
static create(
db: DrizzleDB,
config: ServiceConfig
): UserService {
const emailService = new EmailService(config.email);
const cacheService = new CacheService(config.cache);
const logger = new Logger('UserService');
return new UserService(db, emailService, cacheService, logger);
}
}
// 使用示例
const userService = UserServiceFactory.create(db, appConfig);