发布于 2026-01-06 1 阅读
0

声明式模式简化数据库管理

声明式模式简化数据库管理

今天,我们发布了声明式模式,旨在简化复杂数据库模式的管理和维护。借助声明式模式,您可以以清晰、集中且版本控制的方式定义数据库结构。

⚡️更多发布周信息

什么是声明式模式?

声明式模式将数据库的最终期望状态存储在.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;

Enter fullscreen mode Exit fullscreen mode

与直接修改数据库模式相比,声明式模式具有诸多优势:

  • 单一管理平台。将整个数据库架构维护在一个地方,减少冗余和潜在错误。
  • 版本化迁移。自动生成迁移文件,确保跨环境更新的模式保持一致。将声明式模式文件与项目文件一起存储在版本控制系统中。
  • 简洁的代码审查。无需手动重复复杂的迁移脚本,即可轻松审查对表、视图和函数的更改。

声明式模式与迁移

使用数据库迁移来跟踪和应用数据库更改是最佳实践。每次进行更改时,都会创建一个包含所有更改的新文件,从而保持更改的版本控制和可重现性。

然而,随着数据库模式的复杂性增加,使用版本化迁移进行开发变得越来越困难,因为没有一个地方可以查看整个数据库模式。

例如,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);

Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

这种复杂性会降低开发速度,因为对表的更改可能会破坏其他视图或功能。早在 2022 年初,添加新列这样的简单更改就需要以下步骤。

  1. projects在我们的迁移文件中查找表的最新架构,或者通过查询我们的数据库来查找。
  2. 将该语句写入alter table新的迁移文件中。
  3. 复制并更新projects视图定义,使其包含新列。
  4. 复制并更新触发器函数定义,使其包含新列。
  5. 添加新的 pgTAP 测试并验证现有测试是否通过。
  6. 提交新的迁移文件以供审核,该文件至少有几百行。

这个过程很繁琐,而且让多个工程师projects同时操作会令人沮丧。合并 PR 会导致合并冲突,必须重复步骤 1-5 才能解决。

在生产环境中使用声明式模式

采用声明式模式后,我们的工程师在更新数据库模式时只需在一个界面上进行操作。无需在迁移文件中手动复制受影响的 PostgreSQL 实体,我们只需在一个地方更改模式定义即可。

然后,我们使用像migra这样的模式差异工具,在生成迁移文件时找出视图和函数所需的更新。

例如,现在metadataprojects表中添加新列只需一行差异即可。

--- 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
 );

Enter fullscreen mode Exit fullscreen mode

同样的流程也适用于视图、数据库函数、行级别安全策略、角色授权、自定义类型和约束。虽然生成的迁移文件仍然需要人工审核,但它已将我们的开发时间从数小时缩短到数分钟。此外,针对其他 PR 引入的合并冲突进行 rebase 也变得更加容易。

开始使用声明式模式

Supabase 目前提供声明式模式。

我们将过去两年内部使用的同一套工具添加到了Supabase CLI中。无论您是刚刚开始使用数据库迁移,还是已经厌倦了管理数百个迁移文件,都请尝试声明式模式,它很可能会简化您的开发流程。

请查看我们关于Postgres 语言服务器的博客文章,了解在使用声明式模式进行开发时,如何更好地使用工具和 IDE 集成。

发布周 14

主舞台

第一天 - Supabase UI 库;
第二天 - Supabase Edge 函数:从控制面板部署 + Deno 2.1;
第三天 - 实时:从数据库广播;
第四天 - 用于简化数据库管理的声明式模式

构建阶段

社区聚会

文章来源:https://dev.to/supabase/declarative-schemas-for-simpler-database-management-35ha