首页 / Prisma ORM 入门教程 / 设计模式:Repository、Unit of Work 与架构分层

Prisma ORM 入门教程

设计模式:Repository、Unit of Work 与架构分层

本教程共 54 篇 · 第 46 篇 · 更新于 2026-08-11 · 约 5 分钟阅读

RepositoryUnit of WorkService层单例模式工厂模式NestJSPrismaServiceCQRS

本节目标:学会用 Repository、Unit of Work、Service 层组织 Prisma 代码,理解单例与工厂模式,掌握 NestJS 的 PrismaService 集成。

ORM 解决「怎么查」,设计模式解决「代码往哪放」。业务复杂后,查询散落在路由里,改一个表结构要翻遍全项目。分层是解药。

Repository:把查询细节关进抽屉

Repository 把数据访问封装成类。路由和 Service 只调方法,不碰 Prisma API:

import { PrismaClient, Prisma } from "./generated/prisma/client";

export class UserRepository {
  constructor(private prisma: PrismaClient) {}

  findById(id: string) {
    return this.prisma.user.findUnique({ where: { id } });
  }

  findByEmail(email: string) {
    return this.prisma.user.findUnique({ where: { email } });
  }

  create(data: Prisma.UserCreateInput) {
    return this.prisma.user.create({ data });
  }
}

好处两条:一是封装,软删除过滤、字段脱敏写进仓库,业务代码保持干净;二是可测试,单测 mock 仓库类即可,不用 mock 整个 PrismaClient。

Note

Repository 别过度设计。接口套接口、基类套基类,只会增加理解成本。团队约定「Service 不直接碰 Prisma」就够用,等真的需要换数据库或写单元测试时再加深抽象。

Unit of Work:事务边界就是业务边界

多个写入要一起成功或一起失败,用事务包住。Unit of Work 模式的核心:把事务客户端 tx 注入仓库,让所有仓库共用同一事务:

await prisma.$transaction(async (tx) => {
  const users = new UserRepository(tx);
  const audits = new AuditRepository(tx);

  await users.create(userData);
  await audits.log("user.create");
});

tx 与 PrismaClient 类型兼容,直接传入即可。事务边界划在调用方(Service),仓库不感知事务,职责单一。单步批量写入(createMany、updateMany、deleteMany)本身自动是事务,不需要套交互式事务;交互式事务留给「多步操作必须同生共死」的场景。

Tip

交互式事务默认 5 秒超时,事务内别调外部 API。事务细节见第 26、27 章。

Service 层:编排与校验

Service 层放业务规则:入参校验、权限判断、事务边界、仓库编排:

export class UserService {
  constructor(private prisma: PrismaClient) {}

  async register(input: unknown) {
    const data = createUserSchema.parse(input); // 运行时校验
    return this.prisma.$transaction(async (tx) => {
      const user = await new UserRepository(tx).create(data);
      await new AuditRepository(tx).log("user.register", user.id);
      return user;
    });
  }
}

控制器只管 HTTP,Service 只管业务,Repository 只管数据。三层各司其职,改动互不牵连。

单例与工厂

PrismaClient 每个进程只该有一个实例。每请求 new 一个,就是每请求开一个连接池,数据库很快被拖垮。单例模式:

import { PrismaClient } from "./generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
  });

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}

挂在 globalThis 上,Next.js 热更新不会反复重建客户端。生产环境不用挂,进程内自然只有一个。单例与连接池原理见第 17 章。

工厂模式按需生产客户端。多租户 DB-per-tenant 场景,每个租户一个客户端:

export function createTenantClient(databaseUrl: string) {
  return new PrismaClient({
    adapter: new PrismaPg({ connectionString: databaseUrl }),
  });
}

工厂也常用于测试:批量构造测试数据、生成不同环境的客户端。

NestJS:PrismaService 集成

NestJS 的依赖注入和 Prisma 配合很好。官方推荐把 PrismaClient 包成可注入的 PrismaService(生命周期钩子管理连接、@Global 模块注册一次),完整集成写法见第 47 章,这里不重复。

CQRS 与 DDD:知道边界在哪

最后两个概念,生产项目常被提起。CQRS:读和写分开——写走命令(校验 + 事务),读走查询(优化投影,可缓存)。DDD:聚合根维护业务不变量,值对象是不可变类型,限界上下文对应独立的 Prisma Schema。

Note

小项目不要硬上 CQRS/DDD。它们的价值在复杂业务域;CRUD 为主的应用,Service + Repository 已经足够。真要上,也先从「读模型单独建表」这种低成本改造开始,别一上来就推倒重来。

参考来源

  • Mapagam:Implementing Design Patterns
  • Generalist Programmer:NestJS Prisma Tutorial(原文 v6 写法,已按 v7 改写)
  • Prisma 官方文档:Best practices(连接池与单例)