基于Prisma JS实现SOLID通用DAO服务遇TypeScript类型问题
尝试用Prisma JS构建遵循SOLID原则的DAO类,计划通过通用函数和抽象类结合依赖注入,复用在不同业务模块中(比如Users.Service.ts继承通用类并注入该模块所需的create、update等方法)。但遇到TypeScript类型错误:
This expression is not callable.
Each member of the union type '((args: SelectSubset<T, UsersCreateArgs>) => Prisma__UsersClient<UsersGetPayload, never>) | ((args: SelectSubset<...>) => Prisma__ProductsClient<...>) | ((args: SelectSubset<...>) => Prisma__CartsClient<...>)' has signatures, but none of those signatures are compatible with each other
核心原因是TypeScript无法确定PrismaClient[T]与dto: GenericDto<T>是否为同一类型,添加的类型验证未解决问题,现有可运行实现扩展性极差,寻求类型安全且高扩展的正确实现方式。
现有通用服务代码:
import { Prisma, PrismaClient } from '@prisma/client' export interface IParam<T extends Lowercase<Prisma.ModelName>> { client: PrismaClient[T] data: Dto<T> model: T } type Dto<T extends Lowercase<Prisma.ModelName>> = PrismaClient[T] extends { create: (args: { data: infer U }) => Promise<unknown> } ? U : never type Models<T extends keyof PrismaClient> = PrismaClient[T] extends { create: infer U } ? { create: U } : never type createMethodType<T extends Lowercase<Prisma.ModelName>> = (dto: Dto<T>, client: Models<T>) => Promise<unknown> class CreatorObject { constructor ( public carts = async (dto: Dto<'carts'>, client: Models<'carts'>): Promise<any> => { return await client.create({ data: dto }) }, public users = async (dto: Dto<'users'>, client: Models<'users'>): Promise<any> => { return await client.create({ data: dto }) }, public products = async (dto: Dto<'products'>, client: Models<'products'>): Promise<any> => { return await client.create({ data: dto }) } ) {} } export const clousure = (model: keyof CreatorObject): any => { const creatorObject = new CreatorObject() const create: createMethodType<typeof model> = async (dto, client) => { const response = creatorObject[model] return response } return { create } }
问题本质是TypeScript无法在联合类型中推断出creatorObject[model]的具体调用签名,加上clousure函数返回any丢失类型信息,破坏了类型安全性。以下是重构后的类型安全、高扩展性实现:
1. 通用抽象服务类
定义抽象通用服务类,封装基础CRUD操作,通过泛型绑定具体模型:
import { Prisma, PrismaClient, ModelName } from '@prisma/client'; // 提取模型的CreateDto类型 type CreateDto<M extends ModelName> = Prisma[`${M}CreateArgs`]['data']; // 提取模型的FindUniqueDto类型 type FindUniqueDto<M extends ModelName> = Prisma[`${M}FindUniqueArgs`]['where']; // 提取模型的UpdateDto类型 type UpdateDto<M extends ModelName> = Prisma[`${M}UpdateArgs`]['data']; // 通用服务接口 interface IGenericService<M extends ModelName> { create(data: CreateDto<M>): Promise<Prisma[`${M}GetPayload`]>; findUnique(where: FindUniqueDto<M>): Promise<Prisma[`${M}GetPayload`] | null>; update(where: FindUniqueDto<M>, data: UpdateDto<M>): Promise<Prisma[`${M}GetPayload`]>; } // 通用抽象服务实现 export abstract class GenericService<M extends ModelName> implements IGenericService<M> { protected readonly modelClient: PrismaClient[Lowercase<M>]; constructor(prisma: PrismaClient, modelName: M) { this.modelClient = prisma[Lowercase(modelName) as keyof PrismaClient] as PrismaClient[Lowercase<M>]; } async create(data: CreateDto<M>): Promise<Prisma[`${M}GetPayload`]> { return this.modelClient.create({ data }) as Promise<Prisma[`${M}GetPayload`]>; } async findUnique(where: FindUniqueDto<M>): Promise<Prisma[`${M}GetPayload`] | null> { return this.modelClient.findUnique({ where }) as Promise<Prisma[`${M}GetPayload`] | null>; } async update(where: FindUniqueDto<M>, data: UpdateDto<M>): Promise<Prisma[`${M}GetPayload`]> { return this.modelClient.update({ where, data }) as Promise<Prisma[`${M}GetPayload`]>; } }
2. 具体模块服务继承通用类
以UsersService为例,继承通用类并扩展自定义业务逻辑:
import { PrismaClient, ModelName } from '@prisma/client'; import { GenericService } from './GenericService'; export class UsersService extends GenericService<'User'> { constructor(prisma: PrismaClient) { super(prisma, 'User'); } // 扩展自定义方法:通过邮箱查询用户 async findByEmail(email: string) { return this.findUnique({ email }); } }
3. 依赖注入与使用
初始化PrismaClient并注入到具体服务,实现依赖解耦:
const prisma = new PrismaClient(); const usersService = new UsersService(prisma); // 使用示例 async function run() { const user = await usersService.create({ name: 'John Doe', email: 'john@example.com' }); console.log(user); } run();
关键改进点
- 直接复用Prisma自动生成的类型(如
${M}CreateArgs、${M}GetPayload),无需手动推断,类型准确性更高 - 抽象类封装通用CRUD逻辑,具体服务只需继承并扩展,符合SOLID开闭原则
- 泛型绑定具体模型,TypeScript可准确推断每个服务的参数与返回值类型
- 避免联合类型调用的兼容性问题,每个服务的
modelClient都是对应模型的具体客户端,而非联合类型
内容的提问来源于stack exchange,提问作者Adrian Abadán

