声明式模式简化数据库管理
今天,我们发布了声明式模式,旨在简化复杂数据库模式的管理和维护。借助声明式模式,您可以以清晰、集中且版本控制的方式定义数据库结构。
什么是声明式模式?
声明式模式将数据库的最终期望状态存储在.sql文件中,这些文件可以与项目一起保存和版本控制。例如,以下是一个经典数据库的声明式模式products table:
create table "products" (
"id" serial primary key,
"name" text not null,
"description" text,
"price" numeric(10,2) not null,
"created_at" timestamp default now()
);
alter table "products"
enable row level security;
与直接修改数据库模式相比,声明式模式具有诸多优势:
- 单一管理平台。将整个数据库架构维护在一个地方,减少冗余和潜在错误。
- 版本化迁移。自动生成迁移文件,确保跨环境更新的模式保持一致。将声明式模式文件与项目文件一起存储在版本控制系统中。
- 简洁的代码审查。无需手动重复复杂的迁移脚本,即可轻松审查对表、视图和函数的更改。
声明式模式与迁移
使用数据库迁移来跟踪和应用数据库更改是最佳实践。每次进行更改时,都会创建一个包含所有更改的新文件,从而保持更改的版本控制和可重现性。
然而,随着数据库模式的复杂性增加,使用版本化迁移进行开发变得越来越困难,因为没有一个地方可以查看整个数据库模式。
例如,Supabase 中有一个复杂且经常更新的projects表。以下是启用 RLS 后该表的部分内容:
create table private.projects (
id bigint not null,
name text not null,
organization_id bigint not null,
inserted_at timestamp not null,
updated_at timestamp not null
);
alter table private.projects
enable row level security;
create policy projects_insert
on private.projects
for insert
to authenticated
with check auth.can_write(project_id);
create policy projects_select
on private.projects
for select
to authenticated
using auth.can_read(project_id);
-- Users can only view the projects that they have access to
create view public.projects as select
projects.id,
projects.name,
projects.organization_id,
projects.inserted_at,
projects.updated_at
from private.projects
where auth.can_read(projects.id);
该projects表创建于私有模式中,并公开了一个可供读取的视图。基于属性的访问控制 (ABAC) 是在行级别安全性 (RLS) 策略之上实现的,以确保查询仅返回用户有权访问的项目。
由于 Postgres 视图默认情况下不可更新,我们定义了触发函数,以便在 Supabase 用户创建新项目时,将写入操作级联到底层表。这简化了开发过程,因为projects可以使用常规的 PostgREST 调用插入视图,同时在底层表上调用相应的 RLS 策略。
-- Triggers to update views from PostgREST: select('projects').insert({ ... })
create function public.public_projects_on_insert() returns trigger
as $$
begin
insert into private.projects(
name,
organization_id,
inserted_at,
updated_at
) values (
NEW.name,
NEW.organization_id,
coalesce(NEW.inserted_at, now()),
coalesce(NEW.updated_at, now())
) returning * into NEW;
return NEW;
end
$$ language plpgsql;
create trigger public_projects_on_insert
instead of insert
on public.projects
for each row
execute function public.public_projects_on_insert();
这种复杂性会降低开发速度,因为对表的更改可能会破坏其他视图或功能。早在 2022 年初,添加新列这样的简单更改就需要以下步骤。
projects在我们的迁移文件中查找表的最新架构,或者通过查询我们的数据库来查找。- 将该语句写入
alter table新的迁移文件中。 - 复制并更新
projects视图定义,使其包含新列。 - 复制并更新触发器函数定义,使其包含新列。
- 添加新的 pgTAP 测试并验证现有测试是否通过。
- 提交新的迁移文件以供审核,该文件至少有几百行。
这个过程很繁琐,而且让多个工程师projects同时操作会令人沮丧。合并 PR 会导致合并冲突,必须重复步骤 1-5 才能解决。
在生产环境中使用声明式模式
采用声明式模式后,我们的工程师在更新数据库模式时只需在一个界面上进行操作。无需在迁移文件中手动复制受影响的 PostgreSQL 实体,我们只需在一个地方更改模式定义即可。
然后,我们使用像migra这样的模式差异工具,在生成迁移文件时找出视图和函数所需的更新。
例如,现在metadata向projects表中添加新列只需一行差异即可。
--- a/supabase/schemas/projects.sql
+++ b/supabase/schemas/projects.sql
@@ -2,6 +2,7 @@ create table private.projects (
id bigint not null,
name text not null,
organization_id bigint not null,
+ metadata jsonb,
inserted_at timestamp not null,
updated_at timestamp not null
);
同样的流程也适用于视图、数据库函数、行级别安全策略、角色授权、自定义类型和约束。虽然生成的迁移文件仍然需要人工审核,但它已将我们的开发时间从数小时缩短到数分钟。此外,针对其他 PR 引入的合并冲突进行 rebase 也变得更加容易。
开始使用声明式模式
Supabase 目前提供声明式模式。
- 阅读文档,了解如何在一个地方管理数据库模式并生成版本化的迁移。
- 立即注册 Supabase,开始使用吧!
我们将过去两年内部使用的同一套工具添加到了Supabase CLI中。无论您是刚刚开始使用数据库迁移,还是已经厌倦了管理数百个迁移文件,都请尝试声明式模式,它很可能会简化您的开发流程。
请查看我们关于Postgres 语言服务器的博客文章,了解在使用声明式模式进行开发时,如何更好地使用工具和 IDE 集成。
发布周 14
主舞台
第一天 - Supabase UI 库;
第二天 - Supabase Edge 函数:从控制面板部署 + Deno 2.1;
第三天 - 实时:从数据库广播;
第四天 - 用于简化数据库管理的声明式模式
构建阶段
- 01 - Postgres 语言服务器
- 02 -Supabase 授权:自带收银员-
- 03 -Postgres中的自动嵌入-
- 04 - 最新动态:Supabase Studio 的新增功能
- 05 - 专职泳池维护人员