You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:54:56