服务层架构
概述
项目遵循服务层(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 注释
为服务方法添加清晰的文档注释,说明参数、返回值和行为。