掘金 后端 ( ) • 2024-04-17 10:26

使用 TypeScript 从零搭建自己的 Web 框架:数据库抽象层

什么是 ORM

在 Web 开发中,对象关系映射(Object-Relational Mapping,简称 ORM)是一个非常重要的概念。ORM 允许开发者使用面向对象的方式来操作数据库,而不是直接使用 SQL 语句。这大大提高了开发效率,减少了代码冗余,同时也降低了维护成本。在 TypeScript 中,ORM 的作用更是被进一步放大,因为 TypeScript 提供了强大的类型系统,能够和 ORM 完美结合,为开发者提供更安全、更便捷的开发体验。

什么是 Prisma

Prisma 是一个用 Typescript 和 Rust 编写的介于 ORM 和 SQL Builder 之间的数据库抽象层,它支持多种数据库,包括 PostgreSQL、MySQL、SQLite 等,它既拥有 ORM 的便利性又拥有 SQL Builder 的灵活性,并且可以以类型安全(SafeQL)的方式运行原始查询。Prisma 的核心是一个数据模型定义语言(Data Model Definition Language,简称 DMDL),它允许开发者以直观的方式定义数据库结构和关系。此外,Prisma 还提供了代码生成功能,可以根据数据模型自动生成 TypeScript 类型和 CRUD 操作函数,极大地简化了数据库访问层的开发。

下面,我们将介绍如何在我们的框架中引入 Prisma ORM,并创建一个 PrismaService 来管理数据库操作。

首先,我们需要安装 Prisma CLI 和 Prisma 客户端库。可以通过 npm 或 yarn 进行安装:

npm install @prisma/cli @prisma/client
# 或者
yarn add @prisma/cli @prisma/client

安装完成后,我们需要在项目根目录下创建一个 prisma 文件夹,并在其中创建一个 schema.prisma 文件。这个文件是 Prisma 的数据模型定义文件,用于描述数据库的结构和关系。例如,我们可以定义一个简单的 User 模型:

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

接下来,我们需要使用 Prisma CLI 生成 TypeScript 类型和 CRUD 操作函数。在终端中运行以下命令:

npx prisma generate
# 或者
yarn prisma generate

这个命令会根据 schema.prisma 文件中的定义生成对应的 TypeScript 类型和 CRUD 操作函数,并保存在 node_modules/.prisma/client 目录下。

现在,我们可以在项目中引入 Prisma 客户端库,并创建一个 PrismaService 来管理数据库操作。首先,在项目中创建一个 PrismaService.ts 文件。然后,在文件中引入 Prisma 客户端库,并创建一个 PrismaService 类:

import { Injectable } from '@/core';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService {
  private prisma: PrismaClient;

  constructor() {
    this.prisma = new PrismaClient();
  }

  async getUserById(id: number): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { id },
    });
  }

  // 其他 CRUD 操作函数...
}

在上面的代码中,我们创建了一个 PrismaService 类,并在构造函数中创建了一个 PrismaClient 实例。然后,我们定义了一个 getUserById 方法,用于根据 ID 查询用户信息。这个方法使用了 Prisma 生成的 CRUD 操作函数 findUnique 来实现查询操作。

现在,我们就可以通过依赖注入在项目的其他地方使用 PrismaService 来管理数据库操作了。例如,在控制器中注入 PrismaService 实例,并调用其方法来获取用户信息:

import { Controller, Get, Param } from '@/core';
import { PrismaService } from './services/PrismaService';

@Controller('users')
export class UsersController {
  constructor(private prismaService: PrismaService) {}

  @Get(':id')
  async getUserById(@Param('id') id: number): Promise<User | null> {
    return this.prismaService.getUserById(id);
  }
}

在上面的代码中,我们创建了一个 UsersController 类,并在构造函数中注入了 PrismaService 实例。然后,我们定义了一个 getUserById 方法,用于处理获取用户信息的 HTTP GET 请求。这个方法调用了 PrismaServicegetUserById 方法来获取用户信息,并返回给客户端。

Prisma 工作流

  1. 模型定义

在 Prisma 中,数据模型(datamodel)的定义是工作流的基础。数据模型有两个主要功能:定义底层数据库模式和表,并为 Prisma API 的自动生成的 CRUD(创建、读取、更新、删除)和实时操作代码提供基础。数据模型使用多种构建块来定义,包括:

Types:由多个字段组成,通常表示应用程序中的实体,例如用户(User)、汽车(Car)、订单(Order)等。数据模型中的每种类型都映射到数据库表。 Relations:描述类型之间的关系。 Directives:涵盖不同的用例,例如类型约束或级联删除行为。 Interfaces:作为抽象类型,包括一组字段,类型必须实现这些接口。

  1. 种子数据生产

在定义了数据模型之后,Prisma 支持为数据库生成种子数据。种子数据是预先填充到数据库中的示例数据,用于在开发过程中模拟真实的数据环境。通过编写种子数据脚本,可以为类别和产品等添加假数据,然后运行脚本来填充数据库。这样,开发人员可以在没有真实数据的情况下测试应用程序的功能和性能。

  1. 模型迁移

模型迁移是 Prisma 工作流中的关键步骤,用于同步数据库结构与数据模型的变化。当数据模型发生更改时(例如添加新字段、修改字段类型或删除字段),Prisma 可以生成必要的迁移文件来更新数据库结构。这些迁移文件描述了从旧结构到新结构的转换过程,并且可以通过 Prisma Migrate CLI 工具执行。迁移过程确保了数据库结构与数据模型之间的一致性,从而避免了潜在的数据不一致问题。

总结

通过以上步骤,我们就成功地在 TypeScript 项目中引入了 Prisma,并创建了一个 PrismaService 来管理数据库操作。Prisma 的强大功能和 TypeScript 的类型安全将为我们提供更安全、更便捷的开发体验。