Skip to Content

服务层架构

概述

项目遵循服务层(Service Layer)模式,将数据库操作封装在 @repo/services 包中。应用程序不直接访问 @repo/db,而是通过服务层进行所有数据操作。

架构优势

┌─────────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │ apps/api │────▶│ @repo/ │────▶│ @repo/db │────▶│ Database │ │ apps/admin │ │ services │ └──────────┘ └──────────┘ └─────────────┘ └──────────────┘

集中管理

所有业务逻辑集中在一个地方,易于维护和测试

类型安全

完整的 TypeScript 类型推导,从服务到数据库

代码复用

服务在多个应用间共享,避免重复代码

易于测试

可以轻松 mock 服务层进行单元测试

可用服务

UserService

import { userService } from "@repo/services"; // 查询单个用户 const user = await userService.findById("clx1w2y3z0000abcdef123456"); const userByEmail = await userService.findByEmail("user@example.com"); // 查询多个用户(支持过滤和分页) const users = await userService.findMany({ where: { email: { contains: "@example.com" } }, orderBy: { createdAt: 'desc' }, take: 10, skip: 0 }); // 创建用户 const newUser = await userService.create({ email: "new@example.com", name: "New User" }); // 更新用户 const updated = await userService.update("clx1w2y3z0000abcdef123456", { name: "Updated Name" }); // 删除用户 await userService.delete("clx1w2y3z0000abcdef123456"); // 统计用户数量 const count = await userService.count({ email: { contains: "@gmail.com" } });

在 NestJS 中使用

// apps/api/src/users.service.ts import { Injectable } from "@nestjs/common"; import { userService } from "@repo/services"; import type { User, Prisma } from "@repo/services"; @Injectable() export class UsersService { async findAll(): Promise<User[]> { return await userService.findMany({ orderBy: { createdAt: "desc" }, }); } async findOne(id: string): Promise<User | null> { return await userService.findById(id); } async create(data: Prisma.UserCreateInput): Promise<User> { return await userService.create(data); } }
// apps/api/src/users.controller.ts import { Controller, Get, Post, Body } from "@nestjs/common"; import { UsersService } from "./users.service"; @Controller("users") export class UsersController { constructor(private readonly usersService: UsersService) {} @Get() async findAll() { return await this.usersService.findAll(); } @Post() async create(@Body() createUserDto: any) { return await this.usersService.create(createUserDto); } }

在 Next.js 中使用

// apps/admin/lib/user-actions.ts "use server"; import { userService } from "@repo/services"; import type { User, Prisma } from "@repo/services"; /** * Get all users */ export async function getUsers(): Promise<User[]> { return await userService.findMany({ orderBy: { createdAt: "desc" }, }); } /** * Create a new user */ export async function createUser( data: Prisma.UserCreateInput, ): Promise<User> { return await userService.create(data); }
// apps/admin/app/users/page.tsx import { getUsers } from "@/lib/user-actions"; export default async function UsersPage() { const users = await getUsers(); return ( <div> <h1>Users</h1> {users.map(user => ( <div key={user.id}>{user.name}</div> ))} </div> ); }

创建新服务

1. 创建服务文件

// packages/services/src/product.service.ts import { prisma } from "@repo/db"; import type { Product, Prisma } from "@repo/db"; export class ProductService { async findById(id: string): Promise<Product | null> { return await prisma.product.findUnique({ where: { id }, }); } async findMany(params?: { skip?: number; take?: number; where?: Prisma.ProductWhereInput; orderBy?: Prisma.ProductOrderByWithRelationInput; }): Promise<Product[]> { return await prisma.product.findMany(params); } async create(data: Prisma.ProductCreateInput): Promise<Product> { return await prisma.product.create({ data }); } async update( id: string, data: Prisma.ProductUpdateInput ): Promise<Product> { return await prisma.product.update({ where: { id }, data, }); } async delete(id: string): Promise<Product> { return await prisma.product.delete({ where: { id }, }); } } // 导出单例实例 export const productService = new ProductService();

2. 从 index.ts 导出

// packages/services/src/index.ts export { UserService, userService } from "./user.service"; export { ProductService, productService } from "./product.service"; // Re-export types export type { User, Product, Prisma } from "@repo/db";

3. 更新 package.json exports

// packages/services/package.json { "exports": { ".": "./src/index.ts", "./user": "./src/user.service.ts", "./product": "./src/product.service.ts" } }

4. 在应用中使用

import { productService } from "@repo/services"; const products = await productService.findMany({ where: { price: { gte: 100 } } });

高级模式

事务操作

import { prisma } from "@repo/db"; export class OrderService { async createOrderWithItems( userId: string, productIds: string[] ) { return await prisma.$transaction(async (tx) => { // 创建订单 const order = await tx.order.create({ data: { userId } }); // 创建订单项 await tx.orderItem.createMany({ data: productIds.map(productId => ({ orderId: order.id, productId })) }); return order; }); } }

添加业务逻辑

export class UserService { /** * 查找活跃用户(已验证邮箱且未删除) */ async findActiveUsers(): Promise<User[]> { return await prisma.user.findMany({ where: { emailVerified: true, deletedAt: null }, orderBy: { createdAt: 'desc' } }); } /** * 停用用户(软删除) */ async deactivateUser(id: string): Promise<User> { return await prisma.user.update({ where: { id }, data: { deletedAt: new Date(), active: false } }); } }

最佳实践

不要跳过服务层

  • ❌ 不要在应用中直接导入 @repo/db
  • ✅ 始终通过 @repo/services 访问数据库

保持服务专注

每个服务应该只负责一个实体或相关实体组。例如 UserService 只处理用户,OrderService 只处理订单。

使用事务处理复杂操作

涉及多个表的操作应该包装在事务中,确保数据一致性。

明确返回类型

所有服务方法应该有明确的返回类型(如 Promise<User | null>),提供更好的类型推导。

添加 JSDoc 注释

为服务方法添加清晰的文档注释,说明参数、返回值和行为。